".
+ *
+ * Example ->error('Foo') would yield "error Foo".
+ *
+ * @return string[]
+ */
+ abstract public function getLogs();
+
+ public function testImplements()
+ {
+ $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
+ }
+
+ /**
+ * @dataProvider provideLevelsAndMessages
+ */
+ public function testLogsAtAllLevels($level, $message)
+ {
+ $logger = $this->getLogger();
+ $logger->{$level}($message, array('user' => 'Bob'));
+ $logger->log($level, $message, array('user' => 'Bob'));
+
+ $expected = array(
+ $level.' message of level '.$level.' with context: Bob',
+ $level.' message of level '.$level.' with context: Bob',
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function provideLevelsAndMessages()
+ {
+ return array(
+ LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
+ LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
+ LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
+ LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
+ LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
+ LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
+ LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
+ LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
+ );
+ }
+
+ /**
+ * @expectedException \Psr\Log\InvalidArgumentException
+ */
+ public function testThrowsOnInvalidLevel()
+ {
+ $logger = $this->getLogger();
+ $logger->log('invalid level', 'Foo');
+ }
+
+ public function testContextReplacement()
+ {
+ $logger = $this->getLogger();
+ $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));
+
+ $expected = array('info {Message {nothing} Bob Bar a}');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testObjectCastToString()
+ {
+ if (method_exists($this, 'createPartialMock')) {
+ $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
+ } else {
+ $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
+ }
+ $dummy->expects($this->once())
+ ->method('__toString')
+ ->will($this->returnValue('DUMMY'));
+
+ $this->getLogger()->warning($dummy);
+
+ $expected = array('warning DUMMY');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextCanContainAnything()
+ {
+ $closed = fopen('php://memory', 'r');
+ fclose($closed);
+
+ $context = array(
+ 'bool' => true,
+ 'null' => null,
+ 'string' => 'Foo',
+ 'int' => 0,
+ 'float' => 0.5,
+ 'nested' => array('with object' => new DummyTest),
+ 'object' => new \DateTime,
+ 'resource' => fopen('php://memory', 'r'),
+ 'closed' => $closed,
+ );
+
+ $this->getLogger()->warning('Crazy context data', $context);
+
+ $expected = array('warning Crazy context data');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextExceptionKeyCanBeExceptionOrOtherValues()
+ {
+ $logger = $this->getLogger();
+ $logger->warning('Random message', array('exception' => 'oops'));
+ $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));
+
+ $expected = array(
+ 'warning Random message',
+ 'critical Uncaught Exception!'
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+}
diff --git a/vendor/psr/log/Psr/Log/Test/TestLogger.php b/vendor/psr/log/Psr/Log/Test/TestLogger.php
new file mode 100644
index 000000000..1be323049
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/Test/TestLogger.php
@@ -0,0 +1,147 @@
+ $level,
+ 'message' => $message,
+ 'context' => $context,
+ ];
+
+ $this->recordsByLevel[$record['level']][] = $record;
+ $this->records[] = $record;
+ }
+
+ public function hasRecords($level)
+ {
+ return isset($this->recordsByLevel[$level]);
+ }
+
+ public function hasRecord($record, $level)
+ {
+ if (is_string($record)) {
+ $record = ['message' => $record];
+ }
+ return $this->hasRecordThatPasses(function ($rec) use ($record) {
+ if ($rec['message'] !== $record['message']) {
+ return false;
+ }
+ if (isset($record['context']) && $rec['context'] !== $record['context']) {
+ return false;
+ }
+ return true;
+ }, $level);
+ }
+
+ public function hasRecordThatContains($message, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($message) {
+ return strpos($rec['message'], $message) !== false;
+ }, $level);
+ }
+
+ public function hasRecordThatMatches($regex, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($regex) {
+ return preg_match($regex, $rec['message']) > 0;
+ }, $level);
+ }
+
+ public function hasRecordThatPasses(callable $predicate, $level)
+ {
+ if (!isset($this->recordsByLevel[$level])) {
+ return false;
+ }
+ foreach ($this->recordsByLevel[$level] as $i => $rec) {
+ if (call_user_func($predicate, $rec, $i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function __call($method, $args)
+ {
+ if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
+ $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
+ $level = strtolower($matches[2]);
+ if (method_exists($this, $genericMethod)) {
+ $args[] = $level;
+ return call_user_func_array([$this, $genericMethod], $args);
+ }
+ }
+ throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
+ }
+
+ public function reset()
+ {
+ $this->records = [];
+ $this->recordsByLevel = [];
+ }
+}
diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md
new file mode 100644
index 000000000..a9f20c437
--- /dev/null
+++ b/vendor/psr/log/README.md
@@ -0,0 +1,58 @@
+PSR Log
+=======
+
+This repository holds all interfaces/classes/traits related to
+[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md).
+
+Note that this is not a logger of its own. It is merely an interface that
+describes a logger. See the specification for more details.
+
+Installation
+------------
+
+```bash
+composer require psr/log
+```
+
+Usage
+-----
+
+If you need a logger, you can use the interface like this:
+
+```php
+logger = $logger;
+ }
+
+ public function doSomething()
+ {
+ if ($this->logger) {
+ $this->logger->info('Doing work');
+ }
+
+ try {
+ $this->doSomethingElse();
+ } catch (Exception $exception) {
+ $this->logger->error('Oh no!', array('exception' => $exception));
+ }
+
+ // do something useful
+ }
+}
+```
+
+You can then pick one of the implementations of the interface to get a logger.
+
+If you want to implement the interface, you can require this package and
+implement `Psr\Log\LoggerInterface` in your code. Please read the
+[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
+for details.
diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json
new file mode 100644
index 000000000..ca0569537
--- /dev/null
+++ b/vendor/psr/log/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "psr/log",
+ "description": "Common interface for logging libraries",
+ "keywords": ["psr", "psr-3", "log"],
+ "homepage": "https://github.com/php-fig/log",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ }
+}
diff --git a/vendor/psr/simple-cache/.editorconfig b/vendor/psr/simple-cache/.editorconfig
new file mode 100644
index 000000000..48542cbb4
--- /dev/null
+++ b/vendor/psr/simple-cache/.editorconfig
@@ -0,0 +1,12 @@
+; This file is for unifying the coding style for different editors and IDEs.
+; More information at http://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+indent_size = 4
+indent_style = space
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/vendor/psr/simple-cache/LICENSE.md b/vendor/psr/simple-cache/LICENSE.md
new file mode 100644
index 000000000..e49a7c85a
--- /dev/null
+++ b/vendor/psr/simple-cache/LICENSE.md
@@ -0,0 +1,21 @@
+# The MIT License (MIT)
+
+Copyright (c) 2016 PHP Framework Interoperability Group
+
+> 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.
diff --git a/vendor/psr/simple-cache/README.md b/vendor/psr/simple-cache/README.md
new file mode 100644
index 000000000..43641d175
--- /dev/null
+++ b/vendor/psr/simple-cache/README.md
@@ -0,0 +1,8 @@
+PHP FIG Simple Cache PSR
+========================
+
+This repository holds all interfaces related to PSR-16.
+
+Note that this is not a cache implementation of its own. It is merely an interface that describes a cache implementation. See [the specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) for more details.
+
+You can find implementations of the specification by looking for packages providing the [psr/simple-cache-implementation](https://packagist.org/providers/psr/simple-cache-implementation) virtual package.
diff --git a/vendor/psr/simple-cache/composer.json b/vendor/psr/simple-cache/composer.json
new file mode 100644
index 000000000..2978fa559
--- /dev/null
+++ b/vendor/psr/simple-cache/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "psr/simple-cache",
+ "description": "Common interfaces for simple caching",
+ "keywords": ["psr", "psr-16", "cache", "simple-cache", "caching"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\SimpleCache\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
diff --git a/vendor/psr/simple-cache/src/CacheException.php b/vendor/psr/simple-cache/src/CacheException.php
new file mode 100644
index 000000000..eba53815c
--- /dev/null
+++ b/vendor/psr/simple-cache/src/CacheException.php
@@ -0,0 +1,10 @@
+ value pairs. Cache keys that do not exist or are stale will have $default as value.
+ *
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * MUST be thrown if $keys is neither an array nor a Traversable,
+ * or if any of the $keys are not a legal value.
+ */
+ public function getMultiple($keys, $default = null);
+
+ /**
+ * Persists a set of key => value pairs in the cache, with an optional TTL.
+ *
+ * @param iterable $values A list of key => value pairs for a multiple-set operation.
+ * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and
+ * the driver supports TTL then the library may set a default value
+ * for it or let the driver take care of that.
+ *
+ * @return bool True on success and false on failure.
+ *
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * MUST be thrown if $values is neither an array nor a Traversable,
+ * or if any of the $values are not a legal value.
+ */
+ public function setMultiple($values, $ttl = null);
+
+ /**
+ * Deletes multiple cache items in a single operation.
+ *
+ * @param iterable $keys A list of string-based keys to be deleted.
+ *
+ * @return bool True if the items were successfully removed. False if there was an error.
+ *
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * MUST be thrown if $keys is neither an array nor a Traversable,
+ * or if any of the $keys are not a legal value.
+ */
+ public function deleteMultiple($keys);
+
+ /**
+ * Determines whether an item is present in the cache.
+ *
+ * NOTE: It is recommended that has() is only to be used for cache warming type purposes
+ * and not to be used within your live applications operations for get/set, as this method
+ * is subject to a race condition where your has() will return true and immediately after,
+ * another script can remove it making the state of your app out of date.
+ *
+ * @param string $key The cache item key.
+ *
+ * @return bool
+ *
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * MUST be thrown if the $key string is not a legal value.
+ */
+ public function has($key);
+}
diff --git a/vendor/psr/simple-cache/src/InvalidArgumentException.php b/vendor/psr/simple-cache/src/InvalidArgumentException.php
new file mode 100644
index 000000000..6a9524a20
--- /dev/null
+++ b/vendor/psr/simple-cache/src/InvalidArgumentException.php
@@ -0,0 +1,13 @@
+ 'think\\app\\Service',
+ 1 => 'think\\trace\\Service',
+);
\ No newline at end of file
diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE
new file mode 100644
index 000000000..4cd8bdd30
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php
new file mode 100644
index 000000000..c31611fb8
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/Mbstring.php
@@ -0,0 +1,869 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Mbstring;
+
+/**
+ * Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
+ *
+ * Implemented:
+ * - mb_chr - Returns a specific character from its Unicode code point
+ * - mb_convert_encoding - Convert character encoding
+ * - mb_convert_variables - Convert character code in variable(s)
+ * - mb_decode_mimeheader - Decode string in MIME header field
+ * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
+ * - mb_decode_numericentity - Decode HTML numeric string reference to character
+ * - mb_encode_numericentity - Encode character to HTML numeric string reference
+ * - mb_convert_case - Perform case folding on a string
+ * - mb_detect_encoding - Detect character encoding
+ * - mb_get_info - Get internal settings of mbstring
+ * - mb_http_input - Detect HTTP input character encoding
+ * - mb_http_output - Set/Get HTTP output character encoding
+ * - mb_internal_encoding - Set/Get internal character encoding
+ * - mb_list_encodings - Returns an array of all supported encodings
+ * - mb_ord - Returns the Unicode code point of a character
+ * - mb_output_handler - Callback function converts character encoding in output buffer
+ * - mb_scrub - Replaces ill-formed byte sequences with substitute characters
+ * - mb_strlen - Get string length
+ * - mb_strpos - Find position of first occurrence of string in a string
+ * - mb_strrpos - Find position of last occurrence of a string in a string
+ * - mb_str_split - Convert a string to an array
+ * - mb_strtolower - Make a string lowercase
+ * - mb_strtoupper - Make a string uppercase
+ * - mb_substitute_character - Set/Get substitution character
+ * - mb_substr - Get part of string
+ * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive
+ * - mb_stristr - Finds first occurrence of a string within another, case insensitive
+ * - mb_strrchr - Finds the last occurrence of a character in a string within another
+ * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive
+ * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive
+ * - mb_strstr - Finds first occurrence of a string within another
+ * - mb_strwidth - Return width of string
+ * - mb_substr_count - Count the number of substring occurrences
+ *
+ * Not implemented:
+ * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
+ * - mb_ereg_* - Regular expression with multibyte support
+ * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable
+ * - mb_preferred_mime_name - Get MIME charset string
+ * - mb_regex_encoding - Returns current encoding for multibyte regex as string
+ * - mb_regex_set_options - Set/Get the default options for mbregex functions
+ * - mb_send_mail - Send encoded mail
+ * - mb_split - Split multibyte string using regular expression
+ * - mb_strcut - Get part of string
+ * - mb_strimwidth - Get truncated string with specified width
+ *
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+final class Mbstring
+{
+ public const MB_CASE_FOLD = \PHP_INT_MAX;
+
+ private static $encodingList = ['ASCII', 'UTF-8'];
+ private static $language = 'neutral';
+ private static $internalEncoding = 'UTF-8';
+ private static $caseFold = [
+ ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"],
+ ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'],
+ ];
+
+ public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
+ {
+ if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) {
+ $fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
+ } else {
+ $fromEncoding = self::getEncoding($fromEncoding);
+ }
+
+ $toEncoding = self::getEncoding($toEncoding);
+
+ if ('BASE64' === $fromEncoding) {
+ $s = base64_decode($s);
+ $fromEncoding = $toEncoding;
+ }
+
+ if ('BASE64' === $toEncoding) {
+ return base64_encode($s);
+ }
+
+ if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
+ if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
+ $fromEncoding = 'Windows-1252';
+ }
+ if ('UTF-8' !== $fromEncoding) {
+ $s = \iconv($fromEncoding, 'UTF-8//IGNORE', $s);
+ }
+
+ return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s);
+ }
+
+ if ('HTML-ENTITIES' === $fromEncoding) {
+ $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8');
+ $fromEncoding = 'UTF-8';
+ }
+
+ return \iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
+ }
+
+ public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars)
+ {
+ $ok = true;
+ array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
+ if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
+ $ok = false;
+ }
+ });
+
+ return $ok ? $fromEncoding : false;
+ }
+
+ public static function mb_decode_mimeheader($s)
+ {
+ return \iconv_mime_decode($s, 2, self::$internalEncoding);
+ }
+
+ public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
+ {
+ trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING);
+ }
+
+ public static function mb_decode_numericentity($s, $convmap, $encoding = null)
+ {
+ if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
+ trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);
+
+ return null;
+ }
+
+ if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) {
+ return false;
+ }
+
+ if (null !== $encoding && !is_scalar($encoding)) {
+ trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);
+
+ return ''; // Instead of null (cf. mb_encode_numericentity).
+ }
+
+ $s = (string) $s;
+ if ('' === $s) {
+ return '';
+ }
+
+ $encoding = self::getEncoding($encoding);
+
+ if ('UTF-8' === $encoding) {
+ $encoding = null;
+ if (!preg_match('//u', $s)) {
+ $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s);
+ }
+ } else {
+ $s = \iconv($encoding, 'UTF-8//IGNORE', $s);
+ }
+
+ $cnt = floor(\count($convmap) / 4) * 4;
+
+ for ($i = 0; $i < $cnt; $i += 4) {
+ // collector_decode_htmlnumericentity ignores $convmap[$i + 3]
+ $convmap[$i] += $convmap[$i + 2];
+ $convmap[$i + 1] += $convmap[$i + 2];
+ }
+
+ $s = preg_replace_callback('/(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
+ $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
+ for ($i = 0; $i < $cnt; $i += 4) {
+ if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
+ return self::mb_chr($c - $convmap[$i + 2]);
+ }
+ }
+
+ return $m[0];
+ }, $s);
+
+ if (null === $encoding) {
+ return $s;
+ }
+
+ return \iconv('UTF-8', $encoding.'//IGNORE', $s);
+ }
+
+ public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
+ {
+ if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
+ trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);
+
+ return null;
+ }
+
+ if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) {
+ return false;
+ }
+
+ if (null !== $encoding && !is_scalar($encoding)) {
+ trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);
+
+ return null; // Instead of '' (cf. mb_decode_numericentity).
+ }
+
+ if (null !== $is_hex && !is_scalar($is_hex)) {
+ trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING);
+
+ return null;
+ }
+
+ $s = (string) $s;
+ if ('' === $s) {
+ return '';
+ }
+
+ $encoding = self::getEncoding($encoding);
+
+ if ('UTF-8' === $encoding) {
+ $encoding = null;
+ if (!preg_match('//u', $s)) {
+ $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s);
+ }
+ } else {
+ $s = \iconv($encoding, 'UTF-8//IGNORE', $s);
+ }
+
+ static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
+
+ $cnt = floor(\count($convmap) / 4) * 4;
+ $i = 0;
+ $len = \strlen($s);
+ $result = '';
+
+ while ($i < $len) {
+ $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
+ $uchr = substr($s, $i, $ulen);
+ $i += $ulen;
+ $c = self::mb_ord($uchr);
+
+ for ($j = 0; $j < $cnt; $j += 4) {
+ if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
+ $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
+ $result .= $is_hex ? sprintf('%X;', $cOffset) : ''.$cOffset.';';
+ continue 2;
+ }
+ }
+ $result .= $uchr;
+ }
+
+ if (null === $encoding) {
+ return $result;
+ }
+
+ return \iconv('UTF-8', $encoding.'//IGNORE', $result);
+ }
+
+ public static function mb_convert_case($s, $mode, $encoding = null)
+ {
+ $s = (string) $s;
+ if ('' === $s) {
+ return '';
+ }
+
+ $encoding = self::getEncoding($encoding);
+
+ if ('UTF-8' === $encoding) {
+ $encoding = null;
+ if (!preg_match('//u', $s)) {
+ $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s);
+ }
+ } else {
+ $s = \iconv($encoding, 'UTF-8//IGNORE', $s);
+ }
+
+ if (\MB_CASE_TITLE == $mode) {
+ static $titleRegexp = null;
+ if (null === $titleRegexp) {
+ $titleRegexp = self::getData('titleCaseRegexp');
+ }
+ $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s);
+ } else {
+ if (\MB_CASE_UPPER == $mode) {
+ static $upper = null;
+ if (null === $upper) {
+ $upper = self::getData('upperCase');
+ }
+ $map = $upper;
+ } else {
+ if (self::MB_CASE_FOLD === $mode) {
+ $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s);
+ }
+
+ static $lower = null;
+ if (null === $lower) {
+ $lower = self::getData('lowerCase');
+ }
+ $map = $lower;
+ }
+
+ static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
+
+ $i = 0;
+ $len = \strlen($s);
+
+ while ($i < $len) {
+ $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
+ $uchr = substr($s, $i, $ulen);
+ $i += $ulen;
+
+ if (isset($map[$uchr])) {
+ $uchr = $map[$uchr];
+ $nlen = \strlen($uchr);
+
+ if ($nlen == $ulen) {
+ $nlen = $i;
+ do {
+ $s[--$nlen] = $uchr[--$ulen];
+ } while ($ulen);
+ } else {
+ $s = substr_replace($s, $uchr, $i - $ulen, $ulen);
+ $len += $nlen - $ulen;
+ $i += $nlen - $ulen;
+ }
+ }
+ }
+ }
+
+ if (null === $encoding) {
+ return $s;
+ }
+
+ return \iconv('UTF-8', $encoding.'//IGNORE', $s);
+ }
+
+ public static function mb_internal_encoding($encoding = null)
+ {
+ if (null === $encoding) {
+ return self::$internalEncoding;
+ }
+
+ $normalizedEncoding = self::getEncoding($encoding);
+
+ if ('UTF-8' === $normalizedEncoding || false !== @\iconv($normalizedEncoding, $normalizedEncoding, ' ')) {
+ self::$internalEncoding = $normalizedEncoding;
+
+ return true;
+ }
+
+ if (80000 > \PHP_VERSION_ID) {
+ return false;
+ }
+
+ throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding));
+ }
+
+ public static function mb_language($lang = null)
+ {
+ if (null === $lang) {
+ return self::$language;
+ }
+
+ switch ($normalizedLang = strtolower($lang)) {
+ case 'uni':
+ case 'neutral':
+ self::$language = $normalizedLang;
+
+ return true;
+ }
+
+ if (80000 > \PHP_VERSION_ID) {
+ return false;
+ }
+
+ throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang));
+ }
+
+ public static function mb_list_encodings()
+ {
+ return ['UTF-8'];
+ }
+
+ public static function mb_encoding_aliases($encoding)
+ {
+ switch (strtoupper($encoding)) {
+ case 'UTF8':
+ case 'UTF-8':
+ return ['utf8'];
+ }
+
+ return false;
+ }
+
+ public static function mb_check_encoding($var = null, $encoding = null)
+ {
+ if (null === $encoding) {
+ if (null === $var) {
+ return false;
+ }
+ $encoding = self::$internalEncoding;
+ }
+
+ return self::mb_detect_encoding($var, [$encoding]) || false !== @\iconv($encoding, $encoding, $var);
+ }
+
+ public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
+ {
+ if (null === $encodingList) {
+ $encodingList = self::$encodingList;
+ } else {
+ if (!\is_array($encodingList)) {
+ $encodingList = array_map('trim', explode(',', $encodingList));
+ }
+ $encodingList = array_map('strtoupper', $encodingList);
+ }
+
+ foreach ($encodingList as $enc) {
+ switch ($enc) {
+ case 'ASCII':
+ if (!preg_match('/[\x80-\xFF]/', $str)) {
+ return $enc;
+ }
+ break;
+
+ case 'UTF8':
+ case 'UTF-8':
+ if (preg_match('//u', $str)) {
+ return 'UTF-8';
+ }
+ break;
+
+ default:
+ if (0 === strncmp($enc, 'ISO-8859-', 9)) {
+ return $enc;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public static function mb_detect_order($encodingList = null)
+ {
+ if (null === $encodingList) {
+ return self::$encodingList;
+ }
+
+ if (!\is_array($encodingList)) {
+ $encodingList = array_map('trim', explode(',', $encodingList));
+ }
+ $encodingList = array_map('strtoupper', $encodingList);
+
+ foreach ($encodingList as $enc) {
+ switch ($enc) {
+ default:
+ if (strncmp($enc, 'ISO-8859-', 9)) {
+ return false;
+ }
+ // no break
+ case 'ASCII':
+ case 'UTF8':
+ case 'UTF-8':
+ }
+ }
+
+ self::$encodingList = $encodingList;
+
+ return true;
+ }
+
+ public static function mb_strlen($s, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
+ return \strlen($s);
+ }
+
+ return @\iconv_strlen($s, $encoding);
+ }
+
+ public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
+ return strpos($haystack, $needle, $offset);
+ }
+
+ $needle = (string) $needle;
+ if ('' === $needle) {
+ if (80000 > \PHP_VERSION_ID) {
+ trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING);
+
+ return false;
+ }
+
+ return 0;
+ }
+
+ return \iconv_strpos($haystack, $needle, $offset, $encoding);
+ }
+
+ public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
+ return strrpos($haystack, $needle, $offset);
+ }
+
+ if ($offset != (int) $offset) {
+ $offset = 0;
+ } elseif ($offset = (int) $offset) {
+ if ($offset < 0) {
+ if (0 > $offset += self::mb_strlen($needle)) {
+ $haystack = self::mb_substr($haystack, 0, $offset, $encoding);
+ }
+ $offset = 0;
+ } else {
+ $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
+ }
+ }
+
+ $pos = '' !== $needle || 80000 > \PHP_VERSION_ID
+ ? \iconv_strrpos($haystack, $needle, $encoding)
+ : self::mb_strlen($haystack, $encoding);
+
+ return false !== $pos ? $offset + $pos : false;
+ }
+
+ public static function mb_str_split($string, $split_length = 1, $encoding = null)
+ {
+ if (null !== $string && !is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) {
+ trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING);
+
+ return null;
+ }
+
+ if (1 > $split_length = (int) $split_length) {
+ if (80000 > \PHP_VERSION_ID) {
+ trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING);
+ return false;
+ }
+
+ throw new \ValueError('Argument #2 ($length) must be greater than 0');
+ }
+
+ if (null === $encoding) {
+ $encoding = mb_internal_encoding();
+ }
+
+ if ('UTF-8' === $encoding = self::getEncoding($encoding)) {
+ $rx = '/(';
+ while (65535 < $split_length) {
+ $rx .= '.{65535}';
+ $split_length -= 65535;
+ }
+ $rx .= '.{'.$split_length.'})/us';
+
+ return preg_split($rx, $string, null, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
+ }
+
+ $result = [];
+ $length = mb_strlen($string, $encoding);
+
+ for ($i = 0; $i < $length; $i += $split_length) {
+ $result[] = mb_substr($string, $i, $split_length, $encoding);
+ }
+
+ return $result;
+ }
+
+ public static function mb_strtolower($s, $encoding = null)
+ {
+ return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding);
+ }
+
+ public static function mb_strtoupper($s, $encoding = null)
+ {
+ return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding);
+ }
+
+ public static function mb_substitute_character($c = null)
+ {
+ if (null === $c) {
+ return 'none';
+ }
+ if (0 === strcasecmp($c, 'none')) {
+ return true;
+ }
+ if (80000 > \PHP_VERSION_ID) {
+ return false;
+ }
+
+ throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint');
+ }
+
+ public static function mb_substr($s, $start, $length = null, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
+ return (string) substr($s, $start, null === $length ? 2147483647 : $length);
+ }
+
+ if ($start < 0) {
+ $start = \iconv_strlen($s, $encoding) + $start;
+ if ($start < 0) {
+ $start = 0;
+ }
+ }
+
+ if (null === $length) {
+ $length = 2147483647;
+ } elseif ($length < 0) {
+ $length = \iconv_strlen($s, $encoding) + $length - $start;
+ if ($length < 0) {
+ return '';
+ }
+ }
+
+ return (string) \iconv_substr($s, $start, $length, $encoding);
+ }
+
+ public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
+ {
+ $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
+ $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
+
+ return self::mb_strpos($haystack, $needle, $offset, $encoding);
+ }
+
+ public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
+ {
+ $pos = self::mb_stripos($haystack, $needle, 0, $encoding);
+
+ return self::getSubpart($pos, $part, $haystack, $encoding);
+ }
+
+ public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
+ $pos = strrpos($haystack, $needle);
+ } else {
+ $needle = self::mb_substr($needle, 0, 1, $encoding);
+ $pos = \iconv_strrpos($haystack, $needle, $encoding);
+ }
+
+ return self::getSubpart($pos, $part, $haystack, $encoding);
+ }
+
+ public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
+ {
+ $needle = self::mb_substr($needle, 0, 1, $encoding);
+ $pos = self::mb_strripos($haystack, $needle, $encoding);
+
+ return self::getSubpart($pos, $part, $haystack, $encoding);
+ }
+
+ public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
+ {
+ $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
+ $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
+
+ return self::mb_strrpos($haystack, $needle, $offset, $encoding);
+ }
+
+ public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
+ {
+ $pos = strpos($haystack, $needle);
+ if (false === $pos) {
+ return false;
+ }
+ if ($part) {
+ return substr($haystack, 0, $pos);
+ }
+
+ return substr($haystack, $pos);
+ }
+
+ public static function mb_get_info($type = 'all')
+ {
+ $info = [
+ 'internal_encoding' => self::$internalEncoding,
+ 'http_output' => 'pass',
+ 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
+ 'func_overload' => 0,
+ 'func_overload_list' => 'no overload',
+ 'mail_charset' => 'UTF-8',
+ 'mail_header_encoding' => 'BASE64',
+ 'mail_body_encoding' => 'BASE64',
+ 'illegal_chars' => 0,
+ 'encoding_translation' => 'Off',
+ 'language' => self::$language,
+ 'detect_order' => self::$encodingList,
+ 'substitute_character' => 'none',
+ 'strict_detection' => 'Off',
+ ];
+
+ if ('all' === $type) {
+ return $info;
+ }
+ if (isset($info[$type])) {
+ return $info[$type];
+ }
+
+ return false;
+ }
+
+ public static function mb_http_input($type = '')
+ {
+ return false;
+ }
+
+ public static function mb_http_output($encoding = null)
+ {
+ return null !== $encoding ? 'pass' === $encoding : 'pass';
+ }
+
+ public static function mb_strwidth($s, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+
+ if ('UTF-8' !== $encoding) {
+ $s = \iconv($encoding, 'UTF-8//IGNORE', $s);
+ }
+
+ $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide);
+
+ return ($wide << 1) + \iconv_strlen($s, 'UTF-8');
+ }
+
+ public static function mb_substr_count($haystack, $needle, $encoding = null)
+ {
+ return substr_count($haystack, $needle);
+ }
+
+ public static function mb_output_handler($contents, $status)
+ {
+ return $contents;
+ }
+
+ public static function mb_chr($code, $encoding = null)
+ {
+ if (0x80 > $code %= 0x200000) {
+ $s = \chr($code);
+ } elseif (0x800 > $code) {
+ $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
+ } elseif (0x10000 > $code) {
+ $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
+ } else {
+ $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
+ }
+
+ if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
+ $s = mb_convert_encoding($s, $encoding, 'UTF-8');
+ }
+
+ return $s;
+ }
+
+ public static function mb_ord($s, $encoding = null)
+ {
+ if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
+ $s = mb_convert_encoding($s, 'UTF-8', $encoding);
+ }
+
+ if (1 === \strlen($s)) {
+ return \ord($s);
+ }
+
+ $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
+ if (0xF0 <= $code) {
+ return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
+ }
+ if (0xE0 <= $code) {
+ return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
+ }
+ if (0xC0 <= $code) {
+ return (($code - 0xC0) << 6) + $s[2] - 0x80;
+ }
+
+ return $code;
+ }
+
+ private static function getSubpart($pos, $part, $haystack, $encoding)
+ {
+ if (false === $pos) {
+ return false;
+ }
+ if ($part) {
+ return self::mb_substr($haystack, 0, $pos, $encoding);
+ }
+
+ return self::mb_substr($haystack, $pos, null, $encoding);
+ }
+
+ private static function html_encoding_callback(array $m)
+ {
+ $i = 1;
+ $entities = '';
+ $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8'));
+
+ while (isset($m[$i])) {
+ if (0x80 > $m[$i]) {
+ $entities .= \chr($m[$i++]);
+ continue;
+ }
+ if (0xF0 <= $m[$i]) {
+ $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
+ } elseif (0xE0 <= $m[$i]) {
+ $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
+ } else {
+ $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
+ }
+
+ $entities .= ''.$c.';';
+ }
+
+ return $entities;
+ }
+
+ private static function title_case(array $s)
+ {
+ return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8');
+ }
+
+ private static function getData($file)
+ {
+ if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
+ return require $file;
+ }
+
+ return false;
+ }
+
+ private static function getEncoding($encoding)
+ {
+ if (null === $encoding) {
+ return self::$internalEncoding;
+ }
+
+ if ('UTF-8' === $encoding) {
+ return 'UTF-8';
+ }
+
+ $encoding = strtoupper($encoding);
+
+ if ('8BIT' === $encoding || 'BINARY' === $encoding) {
+ return 'CP850';
+ }
+
+ if ('UTF8' === $encoding) {
+ return 'UTF-8';
+ }
+
+ return $encoding;
+ }
+}
diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md
new file mode 100644
index 000000000..4efb599d8
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/README.md
@@ -0,0 +1,13 @@
+Symfony Polyfill / Mbstring
+===========================
+
+This component provides a partial, native PHP implementation for the
+[Mbstring](https://php.net/mbstring) extension.
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php
new file mode 100644
index 000000000..fac60b081
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php
@@ -0,0 +1,1397 @@
+ 'a',
+ 'B' => 'b',
+ 'C' => 'c',
+ 'D' => 'd',
+ 'E' => 'e',
+ 'F' => 'f',
+ 'G' => 'g',
+ 'H' => 'h',
+ 'I' => 'i',
+ 'J' => 'j',
+ 'K' => 'k',
+ 'L' => 'l',
+ 'M' => 'm',
+ 'N' => 'n',
+ 'O' => 'o',
+ 'P' => 'p',
+ 'Q' => 'q',
+ 'R' => 'r',
+ 'S' => 's',
+ 'T' => 't',
+ 'U' => 'u',
+ 'V' => 'v',
+ 'W' => 'w',
+ 'X' => 'x',
+ 'Y' => 'y',
+ 'Z' => 'z',
+ 'À' => 'à',
+ 'Á' => 'á',
+ 'Â' => 'â',
+ 'Ã' => 'ã',
+ 'Ä' => 'ä',
+ 'Å' => 'å',
+ 'Æ' => 'æ',
+ 'Ç' => 'ç',
+ 'È' => 'è',
+ 'É' => 'é',
+ 'Ê' => 'ê',
+ 'Ë' => 'ë',
+ 'Ì' => 'ì',
+ 'Í' => 'í',
+ 'Î' => 'î',
+ 'Ï' => 'ï',
+ 'Ð' => 'ð',
+ 'Ñ' => 'ñ',
+ 'Ò' => 'ò',
+ 'Ó' => 'ó',
+ 'Ô' => 'ô',
+ 'Õ' => 'õ',
+ 'Ö' => 'ö',
+ 'Ø' => 'ø',
+ 'Ù' => 'ù',
+ 'Ú' => 'ú',
+ 'Û' => 'û',
+ 'Ü' => 'ü',
+ 'Ý' => 'ý',
+ 'Þ' => 'þ',
+ 'Ā' => 'ā',
+ 'Ă' => 'ă',
+ 'Ą' => 'ą',
+ 'Ć' => 'ć',
+ 'Ĉ' => 'ĉ',
+ 'Ċ' => 'ċ',
+ 'Č' => 'č',
+ 'Ď' => 'ď',
+ 'Đ' => 'đ',
+ 'Ē' => 'ē',
+ 'Ĕ' => 'ĕ',
+ 'Ė' => 'ė',
+ 'Ę' => 'ę',
+ 'Ě' => 'ě',
+ 'Ĝ' => 'ĝ',
+ 'Ğ' => 'ğ',
+ 'Ġ' => 'ġ',
+ 'Ģ' => 'ģ',
+ 'Ĥ' => 'ĥ',
+ 'Ħ' => 'ħ',
+ 'Ĩ' => 'ĩ',
+ 'Ī' => 'ī',
+ 'Ĭ' => 'ĭ',
+ 'Į' => 'į',
+ 'İ' => 'i̇',
+ 'IJ' => 'ij',
+ 'Ĵ' => 'ĵ',
+ 'Ķ' => 'ķ',
+ 'Ĺ' => 'ĺ',
+ 'Ļ' => 'ļ',
+ 'Ľ' => 'ľ',
+ 'Ŀ' => 'ŀ',
+ 'Ł' => 'ł',
+ 'Ń' => 'ń',
+ 'Ņ' => 'ņ',
+ 'Ň' => 'ň',
+ 'Ŋ' => 'ŋ',
+ 'Ō' => 'ō',
+ 'Ŏ' => 'ŏ',
+ 'Ő' => 'ő',
+ 'Œ' => 'œ',
+ 'Ŕ' => 'ŕ',
+ 'Ŗ' => 'ŗ',
+ 'Ř' => 'ř',
+ 'Ś' => 'ś',
+ 'Ŝ' => 'ŝ',
+ 'Ş' => 'ş',
+ 'Š' => 'š',
+ 'Ţ' => 'ţ',
+ 'Ť' => 'ť',
+ 'Ŧ' => 'ŧ',
+ 'Ũ' => 'ũ',
+ 'Ū' => 'ū',
+ 'Ŭ' => 'ŭ',
+ 'Ů' => 'ů',
+ 'Ű' => 'ű',
+ 'Ų' => 'ų',
+ 'Ŵ' => 'ŵ',
+ 'Ŷ' => 'ŷ',
+ 'Ÿ' => 'ÿ',
+ 'Ź' => 'ź',
+ 'Ż' => 'ż',
+ 'Ž' => 'ž',
+ 'Ɓ' => 'ɓ',
+ 'Ƃ' => 'ƃ',
+ 'Ƅ' => 'ƅ',
+ 'Ɔ' => 'ɔ',
+ 'Ƈ' => 'ƈ',
+ 'Ɖ' => 'ɖ',
+ 'Ɗ' => 'ɗ',
+ 'Ƌ' => 'ƌ',
+ 'Ǝ' => 'ǝ',
+ 'Ə' => 'ə',
+ 'Ɛ' => 'ɛ',
+ 'Ƒ' => 'ƒ',
+ 'Ɠ' => 'ɠ',
+ 'Ɣ' => 'ɣ',
+ 'Ɩ' => 'ɩ',
+ 'Ɨ' => 'ɨ',
+ 'Ƙ' => 'ƙ',
+ 'Ɯ' => 'ɯ',
+ 'Ɲ' => 'ɲ',
+ 'Ɵ' => 'ɵ',
+ 'Ơ' => 'ơ',
+ 'Ƣ' => 'ƣ',
+ 'Ƥ' => 'ƥ',
+ 'Ʀ' => 'ʀ',
+ 'Ƨ' => 'ƨ',
+ 'Ʃ' => 'ʃ',
+ 'Ƭ' => 'ƭ',
+ 'Ʈ' => 'ʈ',
+ 'Ư' => 'ư',
+ 'Ʊ' => 'ʊ',
+ 'Ʋ' => 'ʋ',
+ 'Ƴ' => 'ƴ',
+ 'Ƶ' => 'ƶ',
+ 'Ʒ' => 'ʒ',
+ 'Ƹ' => 'ƹ',
+ 'Ƽ' => 'ƽ',
+ 'DŽ' => 'dž',
+ 'Dž' => 'dž',
+ 'LJ' => 'lj',
+ 'Lj' => 'lj',
+ 'NJ' => 'nj',
+ 'Nj' => 'nj',
+ 'Ǎ' => 'ǎ',
+ 'Ǐ' => 'ǐ',
+ 'Ǒ' => 'ǒ',
+ 'Ǔ' => 'ǔ',
+ 'Ǖ' => 'ǖ',
+ 'Ǘ' => 'ǘ',
+ 'Ǚ' => 'ǚ',
+ 'Ǜ' => 'ǜ',
+ 'Ǟ' => 'ǟ',
+ 'Ǡ' => 'ǡ',
+ 'Ǣ' => 'ǣ',
+ 'Ǥ' => 'ǥ',
+ 'Ǧ' => 'ǧ',
+ 'Ǩ' => 'ǩ',
+ 'Ǫ' => 'ǫ',
+ 'Ǭ' => 'ǭ',
+ 'Ǯ' => 'ǯ',
+ 'DZ' => 'dz',
+ 'Dz' => 'dz',
+ 'Ǵ' => 'ǵ',
+ 'Ƕ' => 'ƕ',
+ 'Ƿ' => 'ƿ',
+ 'Ǹ' => 'ǹ',
+ 'Ǻ' => 'ǻ',
+ 'Ǽ' => 'ǽ',
+ 'Ǿ' => 'ǿ',
+ 'Ȁ' => 'ȁ',
+ 'Ȃ' => 'ȃ',
+ 'Ȅ' => 'ȅ',
+ 'Ȇ' => 'ȇ',
+ 'Ȉ' => 'ȉ',
+ 'Ȋ' => 'ȋ',
+ 'Ȍ' => 'ȍ',
+ 'Ȏ' => 'ȏ',
+ 'Ȑ' => 'ȑ',
+ 'Ȓ' => 'ȓ',
+ 'Ȕ' => 'ȕ',
+ 'Ȗ' => 'ȗ',
+ 'Ș' => 'ș',
+ 'Ț' => 'ț',
+ 'Ȝ' => 'ȝ',
+ 'Ȟ' => 'ȟ',
+ 'Ƞ' => 'ƞ',
+ 'Ȣ' => 'ȣ',
+ 'Ȥ' => 'ȥ',
+ 'Ȧ' => 'ȧ',
+ 'Ȩ' => 'ȩ',
+ 'Ȫ' => 'ȫ',
+ 'Ȭ' => 'ȭ',
+ 'Ȯ' => 'ȯ',
+ 'Ȱ' => 'ȱ',
+ 'Ȳ' => 'ȳ',
+ 'Ⱥ' => 'ⱥ',
+ 'Ȼ' => 'ȼ',
+ 'Ƚ' => 'ƚ',
+ 'Ⱦ' => 'ⱦ',
+ 'Ɂ' => 'ɂ',
+ 'Ƀ' => 'ƀ',
+ 'Ʉ' => 'ʉ',
+ 'Ʌ' => 'ʌ',
+ 'Ɇ' => 'ɇ',
+ 'Ɉ' => 'ɉ',
+ 'Ɋ' => 'ɋ',
+ 'Ɍ' => 'ɍ',
+ 'Ɏ' => 'ɏ',
+ 'Ͱ' => 'ͱ',
+ 'Ͳ' => 'ͳ',
+ 'Ͷ' => 'ͷ',
+ 'Ϳ' => 'ϳ',
+ 'Ά' => 'ά',
+ 'Έ' => 'έ',
+ 'Ή' => 'ή',
+ 'Ί' => 'ί',
+ 'Ό' => 'ό',
+ 'Ύ' => 'ύ',
+ 'Ώ' => 'ώ',
+ 'Α' => 'α',
+ 'Β' => 'β',
+ 'Γ' => 'γ',
+ 'Δ' => 'δ',
+ 'Ε' => 'ε',
+ 'Ζ' => 'ζ',
+ 'Η' => 'η',
+ 'Θ' => 'θ',
+ 'Ι' => 'ι',
+ 'Κ' => 'κ',
+ 'Λ' => 'λ',
+ 'Μ' => 'μ',
+ 'Ν' => 'ν',
+ 'Ξ' => 'ξ',
+ 'Ο' => 'ο',
+ 'Π' => 'π',
+ 'Ρ' => 'ρ',
+ 'Σ' => 'σ',
+ 'Τ' => 'τ',
+ 'Υ' => 'υ',
+ 'Φ' => 'φ',
+ 'Χ' => 'χ',
+ 'Ψ' => 'ψ',
+ 'Ω' => 'ω',
+ 'Ϊ' => 'ϊ',
+ 'Ϋ' => 'ϋ',
+ 'Ϗ' => 'ϗ',
+ 'Ϙ' => 'ϙ',
+ 'Ϛ' => 'ϛ',
+ 'Ϝ' => 'ϝ',
+ 'Ϟ' => 'ϟ',
+ 'Ϡ' => 'ϡ',
+ 'Ϣ' => 'ϣ',
+ 'Ϥ' => 'ϥ',
+ 'Ϧ' => 'ϧ',
+ 'Ϩ' => 'ϩ',
+ 'Ϫ' => 'ϫ',
+ 'Ϭ' => 'ϭ',
+ 'Ϯ' => 'ϯ',
+ 'ϴ' => 'θ',
+ 'Ϸ' => 'ϸ',
+ 'Ϲ' => 'ϲ',
+ 'Ϻ' => 'ϻ',
+ 'Ͻ' => 'ͻ',
+ 'Ͼ' => 'ͼ',
+ 'Ͽ' => 'ͽ',
+ 'Ѐ' => 'ѐ',
+ 'Ё' => 'ё',
+ 'Ђ' => 'ђ',
+ 'Ѓ' => 'ѓ',
+ 'Є' => 'є',
+ 'Ѕ' => 'ѕ',
+ 'І' => 'і',
+ 'Ї' => 'ї',
+ 'Ј' => 'ј',
+ 'Љ' => 'љ',
+ 'Њ' => 'њ',
+ 'Ћ' => 'ћ',
+ 'Ќ' => 'ќ',
+ 'Ѝ' => 'ѝ',
+ 'Ў' => 'ў',
+ 'Џ' => 'џ',
+ 'А' => 'а',
+ 'Б' => 'б',
+ 'В' => 'в',
+ 'Г' => 'г',
+ 'Д' => 'д',
+ 'Е' => 'е',
+ 'Ж' => 'ж',
+ 'З' => 'з',
+ 'И' => 'и',
+ 'Й' => 'й',
+ 'К' => 'к',
+ 'Л' => 'л',
+ 'М' => 'м',
+ 'Н' => 'н',
+ 'О' => 'о',
+ 'П' => 'п',
+ 'Р' => 'р',
+ 'С' => 'с',
+ 'Т' => 'т',
+ 'У' => 'у',
+ 'Ф' => 'ф',
+ 'Х' => 'х',
+ 'Ц' => 'ц',
+ 'Ч' => 'ч',
+ 'Ш' => 'ш',
+ 'Щ' => 'щ',
+ 'Ъ' => 'ъ',
+ 'Ы' => 'ы',
+ 'Ь' => 'ь',
+ 'Э' => 'э',
+ 'Ю' => 'ю',
+ 'Я' => 'я',
+ 'Ѡ' => 'ѡ',
+ 'Ѣ' => 'ѣ',
+ 'Ѥ' => 'ѥ',
+ 'Ѧ' => 'ѧ',
+ 'Ѩ' => 'ѩ',
+ 'Ѫ' => 'ѫ',
+ 'Ѭ' => 'ѭ',
+ 'Ѯ' => 'ѯ',
+ 'Ѱ' => 'ѱ',
+ 'Ѳ' => 'ѳ',
+ 'Ѵ' => 'ѵ',
+ 'Ѷ' => 'ѷ',
+ 'Ѹ' => 'ѹ',
+ 'Ѻ' => 'ѻ',
+ 'Ѽ' => 'ѽ',
+ 'Ѿ' => 'ѿ',
+ 'Ҁ' => 'ҁ',
+ 'Ҋ' => 'ҋ',
+ 'Ҍ' => 'ҍ',
+ 'Ҏ' => 'ҏ',
+ 'Ґ' => 'ґ',
+ 'Ғ' => 'ғ',
+ 'Ҕ' => 'ҕ',
+ 'Җ' => 'җ',
+ 'Ҙ' => 'ҙ',
+ 'Қ' => 'қ',
+ 'Ҝ' => 'ҝ',
+ 'Ҟ' => 'ҟ',
+ 'Ҡ' => 'ҡ',
+ 'Ң' => 'ң',
+ 'Ҥ' => 'ҥ',
+ 'Ҧ' => 'ҧ',
+ 'Ҩ' => 'ҩ',
+ 'Ҫ' => 'ҫ',
+ 'Ҭ' => 'ҭ',
+ 'Ү' => 'ү',
+ 'Ұ' => 'ұ',
+ 'Ҳ' => 'ҳ',
+ 'Ҵ' => 'ҵ',
+ 'Ҷ' => 'ҷ',
+ 'Ҹ' => 'ҹ',
+ 'Һ' => 'һ',
+ 'Ҽ' => 'ҽ',
+ 'Ҿ' => 'ҿ',
+ 'Ӏ' => 'ӏ',
+ 'Ӂ' => 'ӂ',
+ 'Ӄ' => 'ӄ',
+ 'Ӆ' => 'ӆ',
+ 'Ӈ' => 'ӈ',
+ 'Ӊ' => 'ӊ',
+ 'Ӌ' => 'ӌ',
+ 'Ӎ' => 'ӎ',
+ 'Ӑ' => 'ӑ',
+ 'Ӓ' => 'ӓ',
+ 'Ӕ' => 'ӕ',
+ 'Ӗ' => 'ӗ',
+ 'Ә' => 'ә',
+ 'Ӛ' => 'ӛ',
+ 'Ӝ' => 'ӝ',
+ 'Ӟ' => 'ӟ',
+ 'Ӡ' => 'ӡ',
+ 'Ӣ' => 'ӣ',
+ 'Ӥ' => 'ӥ',
+ 'Ӧ' => 'ӧ',
+ 'Ө' => 'ө',
+ 'Ӫ' => 'ӫ',
+ 'Ӭ' => 'ӭ',
+ 'Ӯ' => 'ӯ',
+ 'Ӱ' => 'ӱ',
+ 'Ӳ' => 'ӳ',
+ 'Ӵ' => 'ӵ',
+ 'Ӷ' => 'ӷ',
+ 'Ӹ' => 'ӹ',
+ 'Ӻ' => 'ӻ',
+ 'Ӽ' => 'ӽ',
+ 'Ӿ' => 'ӿ',
+ 'Ԁ' => 'ԁ',
+ 'Ԃ' => 'ԃ',
+ 'Ԅ' => 'ԅ',
+ 'Ԇ' => 'ԇ',
+ 'Ԉ' => 'ԉ',
+ 'Ԋ' => 'ԋ',
+ 'Ԍ' => 'ԍ',
+ 'Ԏ' => 'ԏ',
+ 'Ԑ' => 'ԑ',
+ 'Ԓ' => 'ԓ',
+ 'Ԕ' => 'ԕ',
+ 'Ԗ' => 'ԗ',
+ 'Ԙ' => 'ԙ',
+ 'Ԛ' => 'ԛ',
+ 'Ԝ' => 'ԝ',
+ 'Ԟ' => 'ԟ',
+ 'Ԡ' => 'ԡ',
+ 'Ԣ' => 'ԣ',
+ 'Ԥ' => 'ԥ',
+ 'Ԧ' => 'ԧ',
+ 'Ԩ' => 'ԩ',
+ 'Ԫ' => 'ԫ',
+ 'Ԭ' => 'ԭ',
+ 'Ԯ' => 'ԯ',
+ 'Ա' => 'ա',
+ 'Բ' => 'բ',
+ 'Գ' => 'գ',
+ 'Դ' => 'դ',
+ 'Ե' => 'ե',
+ 'Զ' => 'զ',
+ 'Է' => 'է',
+ 'Ը' => 'ը',
+ 'Թ' => 'թ',
+ 'Ժ' => 'ժ',
+ 'Ի' => 'ի',
+ 'Լ' => 'լ',
+ 'Խ' => 'խ',
+ 'Ծ' => 'ծ',
+ 'Կ' => 'կ',
+ 'Հ' => 'հ',
+ 'Ձ' => 'ձ',
+ 'Ղ' => 'ղ',
+ 'Ճ' => 'ճ',
+ 'Մ' => 'մ',
+ 'Յ' => 'յ',
+ 'Ն' => 'ն',
+ 'Շ' => 'շ',
+ 'Ո' => 'ո',
+ 'Չ' => 'չ',
+ 'Պ' => 'պ',
+ 'Ջ' => 'ջ',
+ 'Ռ' => 'ռ',
+ 'Ս' => 'ս',
+ 'Վ' => 'վ',
+ 'Տ' => 'տ',
+ 'Ր' => 'ր',
+ 'Ց' => 'ց',
+ 'Ւ' => 'ւ',
+ 'Փ' => 'փ',
+ 'Ք' => 'ք',
+ 'Օ' => 'օ',
+ 'Ֆ' => 'ֆ',
+ 'Ⴀ' => 'ⴀ',
+ 'Ⴁ' => 'ⴁ',
+ 'Ⴂ' => 'ⴂ',
+ 'Ⴃ' => 'ⴃ',
+ 'Ⴄ' => 'ⴄ',
+ 'Ⴅ' => 'ⴅ',
+ 'Ⴆ' => 'ⴆ',
+ 'Ⴇ' => 'ⴇ',
+ 'Ⴈ' => 'ⴈ',
+ 'Ⴉ' => 'ⴉ',
+ 'Ⴊ' => 'ⴊ',
+ 'Ⴋ' => 'ⴋ',
+ 'Ⴌ' => 'ⴌ',
+ 'Ⴍ' => 'ⴍ',
+ 'Ⴎ' => 'ⴎ',
+ 'Ⴏ' => 'ⴏ',
+ 'Ⴐ' => 'ⴐ',
+ 'Ⴑ' => 'ⴑ',
+ 'Ⴒ' => 'ⴒ',
+ 'Ⴓ' => 'ⴓ',
+ 'Ⴔ' => 'ⴔ',
+ 'Ⴕ' => 'ⴕ',
+ 'Ⴖ' => 'ⴖ',
+ 'Ⴗ' => 'ⴗ',
+ 'Ⴘ' => 'ⴘ',
+ 'Ⴙ' => 'ⴙ',
+ 'Ⴚ' => 'ⴚ',
+ 'Ⴛ' => 'ⴛ',
+ 'Ⴜ' => 'ⴜ',
+ 'Ⴝ' => 'ⴝ',
+ 'Ⴞ' => 'ⴞ',
+ 'Ⴟ' => 'ⴟ',
+ 'Ⴠ' => 'ⴠ',
+ 'Ⴡ' => 'ⴡ',
+ 'Ⴢ' => 'ⴢ',
+ 'Ⴣ' => 'ⴣ',
+ 'Ⴤ' => 'ⴤ',
+ 'Ⴥ' => 'ⴥ',
+ 'Ⴧ' => 'ⴧ',
+ 'Ⴭ' => 'ⴭ',
+ 'Ꭰ' => 'ꭰ',
+ 'Ꭱ' => 'ꭱ',
+ 'Ꭲ' => 'ꭲ',
+ 'Ꭳ' => 'ꭳ',
+ 'Ꭴ' => 'ꭴ',
+ 'Ꭵ' => 'ꭵ',
+ 'Ꭶ' => 'ꭶ',
+ 'Ꭷ' => 'ꭷ',
+ 'Ꭸ' => 'ꭸ',
+ 'Ꭹ' => 'ꭹ',
+ 'Ꭺ' => 'ꭺ',
+ 'Ꭻ' => 'ꭻ',
+ 'Ꭼ' => 'ꭼ',
+ 'Ꭽ' => 'ꭽ',
+ 'Ꭾ' => 'ꭾ',
+ 'Ꭿ' => 'ꭿ',
+ 'Ꮀ' => 'ꮀ',
+ 'Ꮁ' => 'ꮁ',
+ 'Ꮂ' => 'ꮂ',
+ 'Ꮃ' => 'ꮃ',
+ 'Ꮄ' => 'ꮄ',
+ 'Ꮅ' => 'ꮅ',
+ 'Ꮆ' => 'ꮆ',
+ 'Ꮇ' => 'ꮇ',
+ 'Ꮈ' => 'ꮈ',
+ 'Ꮉ' => 'ꮉ',
+ 'Ꮊ' => 'ꮊ',
+ 'Ꮋ' => 'ꮋ',
+ 'Ꮌ' => 'ꮌ',
+ 'Ꮍ' => 'ꮍ',
+ 'Ꮎ' => 'ꮎ',
+ 'Ꮏ' => 'ꮏ',
+ 'Ꮐ' => 'ꮐ',
+ 'Ꮑ' => 'ꮑ',
+ 'Ꮒ' => 'ꮒ',
+ 'Ꮓ' => 'ꮓ',
+ 'Ꮔ' => 'ꮔ',
+ 'Ꮕ' => 'ꮕ',
+ 'Ꮖ' => 'ꮖ',
+ 'Ꮗ' => 'ꮗ',
+ 'Ꮘ' => 'ꮘ',
+ 'Ꮙ' => 'ꮙ',
+ 'Ꮚ' => 'ꮚ',
+ 'Ꮛ' => 'ꮛ',
+ 'Ꮜ' => 'ꮜ',
+ 'Ꮝ' => 'ꮝ',
+ 'Ꮞ' => 'ꮞ',
+ 'Ꮟ' => 'ꮟ',
+ 'Ꮠ' => 'ꮠ',
+ 'Ꮡ' => 'ꮡ',
+ 'Ꮢ' => 'ꮢ',
+ 'Ꮣ' => 'ꮣ',
+ 'Ꮤ' => 'ꮤ',
+ 'Ꮥ' => 'ꮥ',
+ 'Ꮦ' => 'ꮦ',
+ 'Ꮧ' => 'ꮧ',
+ 'Ꮨ' => 'ꮨ',
+ 'Ꮩ' => 'ꮩ',
+ 'Ꮪ' => 'ꮪ',
+ 'Ꮫ' => 'ꮫ',
+ 'Ꮬ' => 'ꮬ',
+ 'Ꮭ' => 'ꮭ',
+ 'Ꮮ' => 'ꮮ',
+ 'Ꮯ' => 'ꮯ',
+ 'Ꮰ' => 'ꮰ',
+ 'Ꮱ' => 'ꮱ',
+ 'Ꮲ' => 'ꮲ',
+ 'Ꮳ' => 'ꮳ',
+ 'Ꮴ' => 'ꮴ',
+ 'Ꮵ' => 'ꮵ',
+ 'Ꮶ' => 'ꮶ',
+ 'Ꮷ' => 'ꮷ',
+ 'Ꮸ' => 'ꮸ',
+ 'Ꮹ' => 'ꮹ',
+ 'Ꮺ' => 'ꮺ',
+ 'Ꮻ' => 'ꮻ',
+ 'Ꮼ' => 'ꮼ',
+ 'Ꮽ' => 'ꮽ',
+ 'Ꮾ' => 'ꮾ',
+ 'Ꮿ' => 'ꮿ',
+ 'Ᏸ' => 'ᏸ',
+ 'Ᏹ' => 'ᏹ',
+ 'Ᏺ' => 'ᏺ',
+ 'Ᏻ' => 'ᏻ',
+ 'Ᏼ' => 'ᏼ',
+ 'Ᏽ' => 'ᏽ',
+ 'Ა' => 'ა',
+ 'Ბ' => 'ბ',
+ 'Გ' => 'გ',
+ 'Დ' => 'დ',
+ 'Ე' => 'ე',
+ 'Ვ' => 'ვ',
+ 'Ზ' => 'ზ',
+ 'Თ' => 'თ',
+ 'Ი' => 'ი',
+ 'Კ' => 'კ',
+ 'Ლ' => 'ლ',
+ 'Მ' => 'მ',
+ 'Ნ' => 'ნ',
+ 'Ო' => 'ო',
+ 'Პ' => 'პ',
+ 'Ჟ' => 'ჟ',
+ 'Რ' => 'რ',
+ 'Ს' => 'ს',
+ 'Ტ' => 'ტ',
+ 'Უ' => 'უ',
+ 'Ფ' => 'ფ',
+ 'Ქ' => 'ქ',
+ 'Ღ' => 'ღ',
+ 'Ყ' => 'ყ',
+ 'Შ' => 'შ',
+ 'Ჩ' => 'ჩ',
+ 'Ც' => 'ც',
+ 'Ძ' => 'ძ',
+ 'Წ' => 'წ',
+ 'Ჭ' => 'ჭ',
+ 'Ხ' => 'ხ',
+ 'Ჯ' => 'ჯ',
+ 'Ჰ' => 'ჰ',
+ 'Ჱ' => 'ჱ',
+ 'Ჲ' => 'ჲ',
+ 'Ჳ' => 'ჳ',
+ 'Ჴ' => 'ჴ',
+ 'Ჵ' => 'ჵ',
+ 'Ჶ' => 'ჶ',
+ 'Ჷ' => 'ჷ',
+ 'Ჸ' => 'ჸ',
+ 'Ჹ' => 'ჹ',
+ 'Ჺ' => 'ჺ',
+ 'Ჽ' => 'ჽ',
+ 'Ჾ' => 'ჾ',
+ 'Ჿ' => 'ჿ',
+ 'Ḁ' => 'ḁ',
+ 'Ḃ' => 'ḃ',
+ 'Ḅ' => 'ḅ',
+ 'Ḇ' => 'ḇ',
+ 'Ḉ' => 'ḉ',
+ 'Ḋ' => 'ḋ',
+ 'Ḍ' => 'ḍ',
+ 'Ḏ' => 'ḏ',
+ 'Ḑ' => 'ḑ',
+ 'Ḓ' => 'ḓ',
+ 'Ḕ' => 'ḕ',
+ 'Ḗ' => 'ḗ',
+ 'Ḙ' => 'ḙ',
+ 'Ḛ' => 'ḛ',
+ 'Ḝ' => 'ḝ',
+ 'Ḟ' => 'ḟ',
+ 'Ḡ' => 'ḡ',
+ 'Ḣ' => 'ḣ',
+ 'Ḥ' => 'ḥ',
+ 'Ḧ' => 'ḧ',
+ 'Ḩ' => 'ḩ',
+ 'Ḫ' => 'ḫ',
+ 'Ḭ' => 'ḭ',
+ 'Ḯ' => 'ḯ',
+ 'Ḱ' => 'ḱ',
+ 'Ḳ' => 'ḳ',
+ 'Ḵ' => 'ḵ',
+ 'Ḷ' => 'ḷ',
+ 'Ḹ' => 'ḹ',
+ 'Ḻ' => 'ḻ',
+ 'Ḽ' => 'ḽ',
+ 'Ḿ' => 'ḿ',
+ 'Ṁ' => 'ṁ',
+ 'Ṃ' => 'ṃ',
+ 'Ṅ' => 'ṅ',
+ 'Ṇ' => 'ṇ',
+ 'Ṉ' => 'ṉ',
+ 'Ṋ' => 'ṋ',
+ 'Ṍ' => 'ṍ',
+ 'Ṏ' => 'ṏ',
+ 'Ṑ' => 'ṑ',
+ 'Ṓ' => 'ṓ',
+ 'Ṕ' => 'ṕ',
+ 'Ṗ' => 'ṗ',
+ 'Ṙ' => 'ṙ',
+ 'Ṛ' => 'ṛ',
+ 'Ṝ' => 'ṝ',
+ 'Ṟ' => 'ṟ',
+ 'Ṡ' => 'ṡ',
+ 'Ṣ' => 'ṣ',
+ 'Ṥ' => 'ṥ',
+ 'Ṧ' => 'ṧ',
+ 'Ṩ' => 'ṩ',
+ 'Ṫ' => 'ṫ',
+ 'Ṭ' => 'ṭ',
+ 'Ṯ' => 'ṯ',
+ 'Ṱ' => 'ṱ',
+ 'Ṳ' => 'ṳ',
+ 'Ṵ' => 'ṵ',
+ 'Ṷ' => 'ṷ',
+ 'Ṹ' => 'ṹ',
+ 'Ṻ' => 'ṻ',
+ 'Ṽ' => 'ṽ',
+ 'Ṿ' => 'ṿ',
+ 'Ẁ' => 'ẁ',
+ 'Ẃ' => 'ẃ',
+ 'Ẅ' => 'ẅ',
+ 'Ẇ' => 'ẇ',
+ 'Ẉ' => 'ẉ',
+ 'Ẋ' => 'ẋ',
+ 'Ẍ' => 'ẍ',
+ 'Ẏ' => 'ẏ',
+ 'Ẑ' => 'ẑ',
+ 'Ẓ' => 'ẓ',
+ 'Ẕ' => 'ẕ',
+ 'ẞ' => 'ß',
+ 'Ạ' => 'ạ',
+ 'Ả' => 'ả',
+ 'Ấ' => 'ấ',
+ 'Ầ' => 'ầ',
+ 'Ẩ' => 'ẩ',
+ 'Ẫ' => 'ẫ',
+ 'Ậ' => 'ậ',
+ 'Ắ' => 'ắ',
+ 'Ằ' => 'ằ',
+ 'Ẳ' => 'ẳ',
+ 'Ẵ' => 'ẵ',
+ 'Ặ' => 'ặ',
+ 'Ẹ' => 'ẹ',
+ 'Ẻ' => 'ẻ',
+ 'Ẽ' => 'ẽ',
+ 'Ế' => 'ế',
+ 'Ề' => 'ề',
+ 'Ể' => 'ể',
+ 'Ễ' => 'ễ',
+ 'Ệ' => 'ệ',
+ 'Ỉ' => 'ỉ',
+ 'Ị' => 'ị',
+ 'Ọ' => 'ọ',
+ 'Ỏ' => 'ỏ',
+ 'Ố' => 'ố',
+ 'Ồ' => 'ồ',
+ 'Ổ' => 'ổ',
+ 'Ỗ' => 'ỗ',
+ 'Ộ' => 'ộ',
+ 'Ớ' => 'ớ',
+ 'Ờ' => 'ờ',
+ 'Ở' => 'ở',
+ 'Ỡ' => 'ỡ',
+ 'Ợ' => 'ợ',
+ 'Ụ' => 'ụ',
+ 'Ủ' => 'ủ',
+ 'Ứ' => 'ứ',
+ 'Ừ' => 'ừ',
+ 'Ử' => 'ử',
+ 'Ữ' => 'ữ',
+ 'Ự' => 'ự',
+ 'Ỳ' => 'ỳ',
+ 'Ỵ' => 'ỵ',
+ 'Ỷ' => 'ỷ',
+ 'Ỹ' => 'ỹ',
+ 'Ỻ' => 'ỻ',
+ 'Ỽ' => 'ỽ',
+ 'Ỿ' => 'ỿ',
+ 'Ἀ' => 'ἀ',
+ 'Ἁ' => 'ἁ',
+ 'Ἂ' => 'ἂ',
+ 'Ἃ' => 'ἃ',
+ 'Ἄ' => 'ἄ',
+ 'Ἅ' => 'ἅ',
+ 'Ἆ' => 'ἆ',
+ 'Ἇ' => 'ἇ',
+ 'Ἐ' => 'ἐ',
+ 'Ἑ' => 'ἑ',
+ 'Ἒ' => 'ἒ',
+ 'Ἓ' => 'ἓ',
+ 'Ἔ' => 'ἔ',
+ 'Ἕ' => 'ἕ',
+ 'Ἠ' => 'ἠ',
+ 'Ἡ' => 'ἡ',
+ 'Ἢ' => 'ἢ',
+ 'Ἣ' => 'ἣ',
+ 'Ἤ' => 'ἤ',
+ 'Ἥ' => 'ἥ',
+ 'Ἦ' => 'ἦ',
+ 'Ἧ' => 'ἧ',
+ 'Ἰ' => 'ἰ',
+ 'Ἱ' => 'ἱ',
+ 'Ἲ' => 'ἲ',
+ 'Ἳ' => 'ἳ',
+ 'Ἴ' => 'ἴ',
+ 'Ἵ' => 'ἵ',
+ 'Ἶ' => 'ἶ',
+ 'Ἷ' => 'ἷ',
+ 'Ὀ' => 'ὀ',
+ 'Ὁ' => 'ὁ',
+ 'Ὂ' => 'ὂ',
+ 'Ὃ' => 'ὃ',
+ 'Ὄ' => 'ὄ',
+ 'Ὅ' => 'ὅ',
+ 'Ὑ' => 'ὑ',
+ 'Ὓ' => 'ὓ',
+ 'Ὕ' => 'ὕ',
+ 'Ὗ' => 'ὗ',
+ 'Ὠ' => 'ὠ',
+ 'Ὡ' => 'ὡ',
+ 'Ὢ' => 'ὢ',
+ 'Ὣ' => 'ὣ',
+ 'Ὤ' => 'ὤ',
+ 'Ὥ' => 'ὥ',
+ 'Ὦ' => 'ὦ',
+ 'Ὧ' => 'ὧ',
+ 'ᾈ' => 'ᾀ',
+ 'ᾉ' => 'ᾁ',
+ 'ᾊ' => 'ᾂ',
+ 'ᾋ' => 'ᾃ',
+ 'ᾌ' => 'ᾄ',
+ 'ᾍ' => 'ᾅ',
+ 'ᾎ' => 'ᾆ',
+ 'ᾏ' => 'ᾇ',
+ 'ᾘ' => 'ᾐ',
+ 'ᾙ' => 'ᾑ',
+ 'ᾚ' => 'ᾒ',
+ 'ᾛ' => 'ᾓ',
+ 'ᾜ' => 'ᾔ',
+ 'ᾝ' => 'ᾕ',
+ 'ᾞ' => 'ᾖ',
+ 'ᾟ' => 'ᾗ',
+ 'ᾨ' => 'ᾠ',
+ 'ᾩ' => 'ᾡ',
+ 'ᾪ' => 'ᾢ',
+ 'ᾫ' => 'ᾣ',
+ 'ᾬ' => 'ᾤ',
+ 'ᾭ' => 'ᾥ',
+ 'ᾮ' => 'ᾦ',
+ 'ᾯ' => 'ᾧ',
+ 'Ᾰ' => 'ᾰ',
+ 'Ᾱ' => 'ᾱ',
+ 'Ὰ' => 'ὰ',
+ 'Ά' => 'ά',
+ 'ᾼ' => 'ᾳ',
+ 'Ὲ' => 'ὲ',
+ 'Έ' => 'έ',
+ 'Ὴ' => 'ὴ',
+ 'Ή' => 'ή',
+ 'ῌ' => 'ῃ',
+ 'Ῐ' => 'ῐ',
+ 'Ῑ' => 'ῑ',
+ 'Ὶ' => 'ὶ',
+ 'Ί' => 'ί',
+ 'Ῠ' => 'ῠ',
+ 'Ῡ' => 'ῡ',
+ 'Ὺ' => 'ὺ',
+ 'Ύ' => 'ύ',
+ 'Ῥ' => 'ῥ',
+ 'Ὸ' => 'ὸ',
+ 'Ό' => 'ό',
+ 'Ὼ' => 'ὼ',
+ 'Ώ' => 'ώ',
+ 'ῼ' => 'ῳ',
+ 'Ω' => 'ω',
+ 'K' => 'k',
+ 'Å' => 'å',
+ 'Ⅎ' => 'ⅎ',
+ 'Ⅰ' => 'ⅰ',
+ 'Ⅱ' => 'ⅱ',
+ 'Ⅲ' => 'ⅲ',
+ 'Ⅳ' => 'ⅳ',
+ 'Ⅴ' => 'ⅴ',
+ 'Ⅵ' => 'ⅵ',
+ 'Ⅶ' => 'ⅶ',
+ 'Ⅷ' => 'ⅷ',
+ 'Ⅸ' => 'ⅸ',
+ 'Ⅹ' => 'ⅹ',
+ 'Ⅺ' => 'ⅺ',
+ 'Ⅻ' => 'ⅻ',
+ 'Ⅼ' => 'ⅼ',
+ 'Ⅽ' => 'ⅽ',
+ 'Ⅾ' => 'ⅾ',
+ 'Ⅿ' => 'ⅿ',
+ 'Ↄ' => 'ↄ',
+ 'Ⓐ' => 'ⓐ',
+ 'Ⓑ' => 'ⓑ',
+ 'Ⓒ' => 'ⓒ',
+ 'Ⓓ' => 'ⓓ',
+ 'Ⓔ' => 'ⓔ',
+ 'Ⓕ' => 'ⓕ',
+ 'Ⓖ' => 'ⓖ',
+ 'Ⓗ' => 'ⓗ',
+ 'Ⓘ' => 'ⓘ',
+ 'Ⓙ' => 'ⓙ',
+ 'Ⓚ' => 'ⓚ',
+ 'Ⓛ' => 'ⓛ',
+ 'Ⓜ' => 'ⓜ',
+ 'Ⓝ' => 'ⓝ',
+ 'Ⓞ' => 'ⓞ',
+ 'Ⓟ' => 'ⓟ',
+ 'Ⓠ' => 'ⓠ',
+ 'Ⓡ' => 'ⓡ',
+ 'Ⓢ' => 'ⓢ',
+ 'Ⓣ' => 'ⓣ',
+ 'Ⓤ' => 'ⓤ',
+ 'Ⓥ' => 'ⓥ',
+ 'Ⓦ' => 'ⓦ',
+ 'Ⓧ' => 'ⓧ',
+ 'Ⓨ' => 'ⓨ',
+ 'Ⓩ' => 'ⓩ',
+ 'Ⰰ' => 'ⰰ',
+ 'Ⰱ' => 'ⰱ',
+ 'Ⰲ' => 'ⰲ',
+ 'Ⰳ' => 'ⰳ',
+ 'Ⰴ' => 'ⰴ',
+ 'Ⰵ' => 'ⰵ',
+ 'Ⰶ' => 'ⰶ',
+ 'Ⰷ' => 'ⰷ',
+ 'Ⰸ' => 'ⰸ',
+ 'Ⰹ' => 'ⰹ',
+ 'Ⰺ' => 'ⰺ',
+ 'Ⰻ' => 'ⰻ',
+ 'Ⰼ' => 'ⰼ',
+ 'Ⰽ' => 'ⰽ',
+ 'Ⰾ' => 'ⰾ',
+ 'Ⰿ' => 'ⰿ',
+ 'Ⱀ' => 'ⱀ',
+ 'Ⱁ' => 'ⱁ',
+ 'Ⱂ' => 'ⱂ',
+ 'Ⱃ' => 'ⱃ',
+ 'Ⱄ' => 'ⱄ',
+ 'Ⱅ' => 'ⱅ',
+ 'Ⱆ' => 'ⱆ',
+ 'Ⱇ' => 'ⱇ',
+ 'Ⱈ' => 'ⱈ',
+ 'Ⱉ' => 'ⱉ',
+ 'Ⱊ' => 'ⱊ',
+ 'Ⱋ' => 'ⱋ',
+ 'Ⱌ' => 'ⱌ',
+ 'Ⱍ' => 'ⱍ',
+ 'Ⱎ' => 'ⱎ',
+ 'Ⱏ' => 'ⱏ',
+ 'Ⱐ' => 'ⱐ',
+ 'Ⱑ' => 'ⱑ',
+ 'Ⱒ' => 'ⱒ',
+ 'Ⱓ' => 'ⱓ',
+ 'Ⱔ' => 'ⱔ',
+ 'Ⱕ' => 'ⱕ',
+ 'Ⱖ' => 'ⱖ',
+ 'Ⱗ' => 'ⱗ',
+ 'Ⱘ' => 'ⱘ',
+ 'Ⱙ' => 'ⱙ',
+ 'Ⱚ' => 'ⱚ',
+ 'Ⱛ' => 'ⱛ',
+ 'Ⱜ' => 'ⱜ',
+ 'Ⱝ' => 'ⱝ',
+ 'Ⱞ' => 'ⱞ',
+ 'Ⱡ' => 'ⱡ',
+ 'Ɫ' => 'ɫ',
+ 'Ᵽ' => 'ᵽ',
+ 'Ɽ' => 'ɽ',
+ 'Ⱨ' => 'ⱨ',
+ 'Ⱪ' => 'ⱪ',
+ 'Ⱬ' => 'ⱬ',
+ 'Ɑ' => 'ɑ',
+ 'Ɱ' => 'ɱ',
+ 'Ɐ' => 'ɐ',
+ 'Ɒ' => 'ɒ',
+ 'Ⱳ' => 'ⱳ',
+ 'Ⱶ' => 'ⱶ',
+ 'Ȿ' => 'ȿ',
+ 'Ɀ' => 'ɀ',
+ 'Ⲁ' => 'ⲁ',
+ 'Ⲃ' => 'ⲃ',
+ 'Ⲅ' => 'ⲅ',
+ 'Ⲇ' => 'ⲇ',
+ 'Ⲉ' => 'ⲉ',
+ 'Ⲋ' => 'ⲋ',
+ 'Ⲍ' => 'ⲍ',
+ 'Ⲏ' => 'ⲏ',
+ 'Ⲑ' => 'ⲑ',
+ 'Ⲓ' => 'ⲓ',
+ 'Ⲕ' => 'ⲕ',
+ 'Ⲗ' => 'ⲗ',
+ 'Ⲙ' => 'ⲙ',
+ 'Ⲛ' => 'ⲛ',
+ 'Ⲝ' => 'ⲝ',
+ 'Ⲟ' => 'ⲟ',
+ 'Ⲡ' => 'ⲡ',
+ 'Ⲣ' => 'ⲣ',
+ 'Ⲥ' => 'ⲥ',
+ 'Ⲧ' => 'ⲧ',
+ 'Ⲩ' => 'ⲩ',
+ 'Ⲫ' => 'ⲫ',
+ 'Ⲭ' => 'ⲭ',
+ 'Ⲯ' => 'ⲯ',
+ 'Ⲱ' => 'ⲱ',
+ 'Ⲳ' => 'ⲳ',
+ 'Ⲵ' => 'ⲵ',
+ 'Ⲷ' => 'ⲷ',
+ 'Ⲹ' => 'ⲹ',
+ 'Ⲻ' => 'ⲻ',
+ 'Ⲽ' => 'ⲽ',
+ 'Ⲿ' => 'ⲿ',
+ 'Ⳁ' => 'ⳁ',
+ 'Ⳃ' => 'ⳃ',
+ 'Ⳅ' => 'ⳅ',
+ 'Ⳇ' => 'ⳇ',
+ 'Ⳉ' => 'ⳉ',
+ 'Ⳋ' => 'ⳋ',
+ 'Ⳍ' => 'ⳍ',
+ 'Ⳏ' => 'ⳏ',
+ 'Ⳑ' => 'ⳑ',
+ 'Ⳓ' => 'ⳓ',
+ 'Ⳕ' => 'ⳕ',
+ 'Ⳗ' => 'ⳗ',
+ 'Ⳙ' => 'ⳙ',
+ 'Ⳛ' => 'ⳛ',
+ 'Ⳝ' => 'ⳝ',
+ 'Ⳟ' => 'ⳟ',
+ 'Ⳡ' => 'ⳡ',
+ 'Ⳣ' => 'ⳣ',
+ 'Ⳬ' => 'ⳬ',
+ 'Ⳮ' => 'ⳮ',
+ 'Ⳳ' => 'ⳳ',
+ 'Ꙁ' => 'ꙁ',
+ 'Ꙃ' => 'ꙃ',
+ 'Ꙅ' => 'ꙅ',
+ 'Ꙇ' => 'ꙇ',
+ 'Ꙉ' => 'ꙉ',
+ 'Ꙋ' => 'ꙋ',
+ 'Ꙍ' => 'ꙍ',
+ 'Ꙏ' => 'ꙏ',
+ 'Ꙑ' => 'ꙑ',
+ 'Ꙓ' => 'ꙓ',
+ 'Ꙕ' => 'ꙕ',
+ 'Ꙗ' => 'ꙗ',
+ 'Ꙙ' => 'ꙙ',
+ 'Ꙛ' => 'ꙛ',
+ 'Ꙝ' => 'ꙝ',
+ 'Ꙟ' => 'ꙟ',
+ 'Ꙡ' => 'ꙡ',
+ 'Ꙣ' => 'ꙣ',
+ 'Ꙥ' => 'ꙥ',
+ 'Ꙧ' => 'ꙧ',
+ 'Ꙩ' => 'ꙩ',
+ 'Ꙫ' => 'ꙫ',
+ 'Ꙭ' => 'ꙭ',
+ 'Ꚁ' => 'ꚁ',
+ 'Ꚃ' => 'ꚃ',
+ 'Ꚅ' => 'ꚅ',
+ 'Ꚇ' => 'ꚇ',
+ 'Ꚉ' => 'ꚉ',
+ 'Ꚋ' => 'ꚋ',
+ 'Ꚍ' => 'ꚍ',
+ 'Ꚏ' => 'ꚏ',
+ 'Ꚑ' => 'ꚑ',
+ 'Ꚓ' => 'ꚓ',
+ 'Ꚕ' => 'ꚕ',
+ 'Ꚗ' => 'ꚗ',
+ 'Ꚙ' => 'ꚙ',
+ 'Ꚛ' => 'ꚛ',
+ 'Ꜣ' => 'ꜣ',
+ 'Ꜥ' => 'ꜥ',
+ 'Ꜧ' => 'ꜧ',
+ 'Ꜩ' => 'ꜩ',
+ 'Ꜫ' => 'ꜫ',
+ 'Ꜭ' => 'ꜭ',
+ 'Ꜯ' => 'ꜯ',
+ 'Ꜳ' => 'ꜳ',
+ 'Ꜵ' => 'ꜵ',
+ 'Ꜷ' => 'ꜷ',
+ 'Ꜹ' => 'ꜹ',
+ 'Ꜻ' => 'ꜻ',
+ 'Ꜽ' => 'ꜽ',
+ 'Ꜿ' => 'ꜿ',
+ 'Ꝁ' => 'ꝁ',
+ 'Ꝃ' => 'ꝃ',
+ 'Ꝅ' => 'ꝅ',
+ 'Ꝇ' => 'ꝇ',
+ 'Ꝉ' => 'ꝉ',
+ 'Ꝋ' => 'ꝋ',
+ 'Ꝍ' => 'ꝍ',
+ 'Ꝏ' => 'ꝏ',
+ 'Ꝑ' => 'ꝑ',
+ 'Ꝓ' => 'ꝓ',
+ 'Ꝕ' => 'ꝕ',
+ 'Ꝗ' => 'ꝗ',
+ 'Ꝙ' => 'ꝙ',
+ 'Ꝛ' => 'ꝛ',
+ 'Ꝝ' => 'ꝝ',
+ 'Ꝟ' => 'ꝟ',
+ 'Ꝡ' => 'ꝡ',
+ 'Ꝣ' => 'ꝣ',
+ 'Ꝥ' => 'ꝥ',
+ 'Ꝧ' => 'ꝧ',
+ 'Ꝩ' => 'ꝩ',
+ 'Ꝫ' => 'ꝫ',
+ 'Ꝭ' => 'ꝭ',
+ 'Ꝯ' => 'ꝯ',
+ 'Ꝺ' => 'ꝺ',
+ 'Ꝼ' => 'ꝼ',
+ 'Ᵹ' => 'ᵹ',
+ 'Ꝿ' => 'ꝿ',
+ 'Ꞁ' => 'ꞁ',
+ 'Ꞃ' => 'ꞃ',
+ 'Ꞅ' => 'ꞅ',
+ 'Ꞇ' => 'ꞇ',
+ 'Ꞌ' => 'ꞌ',
+ 'Ɥ' => 'ɥ',
+ 'Ꞑ' => 'ꞑ',
+ 'Ꞓ' => 'ꞓ',
+ 'Ꞗ' => 'ꞗ',
+ 'Ꞙ' => 'ꞙ',
+ 'Ꞛ' => 'ꞛ',
+ 'Ꞝ' => 'ꞝ',
+ 'Ꞟ' => 'ꞟ',
+ 'Ꞡ' => 'ꞡ',
+ 'Ꞣ' => 'ꞣ',
+ 'Ꞥ' => 'ꞥ',
+ 'Ꞧ' => 'ꞧ',
+ 'Ꞩ' => 'ꞩ',
+ 'Ɦ' => 'ɦ',
+ 'Ɜ' => 'ɜ',
+ 'Ɡ' => 'ɡ',
+ 'Ɬ' => 'ɬ',
+ 'Ɪ' => 'ɪ',
+ 'Ʞ' => 'ʞ',
+ 'Ʇ' => 'ʇ',
+ 'Ʝ' => 'ʝ',
+ 'Ꭓ' => 'ꭓ',
+ 'Ꞵ' => 'ꞵ',
+ 'Ꞷ' => 'ꞷ',
+ 'Ꞹ' => 'ꞹ',
+ 'Ꞻ' => 'ꞻ',
+ 'Ꞽ' => 'ꞽ',
+ 'Ꞿ' => 'ꞿ',
+ 'Ꟃ' => 'ꟃ',
+ 'Ꞔ' => 'ꞔ',
+ 'Ʂ' => 'ʂ',
+ 'Ᶎ' => 'ᶎ',
+ 'Ꟈ' => 'ꟈ',
+ 'Ꟊ' => 'ꟊ',
+ 'Ꟶ' => 'ꟶ',
+ 'A' => 'a',
+ 'B' => 'b',
+ 'C' => 'c',
+ 'D' => 'd',
+ 'E' => 'e',
+ 'F' => 'f',
+ 'G' => 'g',
+ 'H' => 'h',
+ 'I' => 'i',
+ 'J' => 'j',
+ 'K' => 'k',
+ 'L' => 'l',
+ 'M' => 'm',
+ 'N' => 'n',
+ 'O' => 'o',
+ 'P' => 'p',
+ 'Q' => 'q',
+ 'R' => 'r',
+ 'S' => 's',
+ 'T' => 't',
+ 'U' => 'u',
+ 'V' => 'v',
+ 'W' => 'w',
+ 'X' => 'x',
+ 'Y' => 'y',
+ 'Z' => 'z',
+ '𐐀' => '𐐨',
+ '𐐁' => '𐐩',
+ '𐐂' => '𐐪',
+ '𐐃' => '𐐫',
+ '𐐄' => '𐐬',
+ '𐐅' => '𐐭',
+ '𐐆' => '𐐮',
+ '𐐇' => '𐐯',
+ '𐐈' => '𐐰',
+ '𐐉' => '𐐱',
+ '𐐊' => '𐐲',
+ '𐐋' => '𐐳',
+ '𐐌' => '𐐴',
+ '𐐍' => '𐐵',
+ '𐐎' => '𐐶',
+ '𐐏' => '𐐷',
+ '𐐐' => '𐐸',
+ '𐐑' => '𐐹',
+ '𐐒' => '𐐺',
+ '𐐓' => '𐐻',
+ '𐐔' => '𐐼',
+ '𐐕' => '𐐽',
+ '𐐖' => '𐐾',
+ '𐐗' => '𐐿',
+ '𐐘' => '𐑀',
+ '𐐙' => '𐑁',
+ '𐐚' => '𐑂',
+ '𐐛' => '𐑃',
+ '𐐜' => '𐑄',
+ '𐐝' => '𐑅',
+ '𐐞' => '𐑆',
+ '𐐟' => '𐑇',
+ '𐐠' => '𐑈',
+ '𐐡' => '𐑉',
+ '𐐢' => '𐑊',
+ '𐐣' => '𐑋',
+ '𐐤' => '𐑌',
+ '𐐥' => '𐑍',
+ '𐐦' => '𐑎',
+ '𐐧' => '𐑏',
+ '𐒰' => '𐓘',
+ '𐒱' => '𐓙',
+ '𐒲' => '𐓚',
+ '𐒳' => '𐓛',
+ '𐒴' => '𐓜',
+ '𐒵' => '𐓝',
+ '𐒶' => '𐓞',
+ '𐒷' => '𐓟',
+ '𐒸' => '𐓠',
+ '𐒹' => '𐓡',
+ '𐒺' => '𐓢',
+ '𐒻' => '𐓣',
+ '𐒼' => '𐓤',
+ '𐒽' => '𐓥',
+ '𐒾' => '𐓦',
+ '𐒿' => '𐓧',
+ '𐓀' => '𐓨',
+ '𐓁' => '𐓩',
+ '𐓂' => '𐓪',
+ '𐓃' => '𐓫',
+ '𐓄' => '𐓬',
+ '𐓅' => '𐓭',
+ '𐓆' => '𐓮',
+ '𐓇' => '𐓯',
+ '𐓈' => '𐓰',
+ '𐓉' => '𐓱',
+ '𐓊' => '𐓲',
+ '𐓋' => '𐓳',
+ '𐓌' => '𐓴',
+ '𐓍' => '𐓵',
+ '𐓎' => '𐓶',
+ '𐓏' => '𐓷',
+ '𐓐' => '𐓸',
+ '𐓑' => '𐓹',
+ '𐓒' => '𐓺',
+ '𐓓' => '𐓻',
+ '𐲀' => '𐳀',
+ '𐲁' => '𐳁',
+ '𐲂' => '𐳂',
+ '𐲃' => '𐳃',
+ '𐲄' => '𐳄',
+ '𐲅' => '𐳅',
+ '𐲆' => '𐳆',
+ '𐲇' => '𐳇',
+ '𐲈' => '𐳈',
+ '𐲉' => '𐳉',
+ '𐲊' => '𐳊',
+ '𐲋' => '𐳋',
+ '𐲌' => '𐳌',
+ '𐲍' => '𐳍',
+ '𐲎' => '𐳎',
+ '𐲏' => '𐳏',
+ '𐲐' => '𐳐',
+ '𐲑' => '𐳑',
+ '𐲒' => '𐳒',
+ '𐲓' => '𐳓',
+ '𐲔' => '𐳔',
+ '𐲕' => '𐳕',
+ '𐲖' => '𐳖',
+ '𐲗' => '𐳗',
+ '𐲘' => '𐳘',
+ '𐲙' => '𐳙',
+ '𐲚' => '𐳚',
+ '𐲛' => '𐳛',
+ '𐲜' => '𐳜',
+ '𐲝' => '𐳝',
+ '𐲞' => '𐳞',
+ '𐲟' => '𐳟',
+ '𐲠' => '𐳠',
+ '𐲡' => '𐳡',
+ '𐲢' => '𐳢',
+ '𐲣' => '𐳣',
+ '𐲤' => '𐳤',
+ '𐲥' => '𐳥',
+ '𐲦' => '𐳦',
+ '𐲧' => '𐳧',
+ '𐲨' => '𐳨',
+ '𐲩' => '𐳩',
+ '𐲪' => '𐳪',
+ '𐲫' => '𐳫',
+ '𐲬' => '𐳬',
+ '𐲭' => '𐳭',
+ '𐲮' => '𐳮',
+ '𐲯' => '𐳯',
+ '𐲰' => '𐳰',
+ '𐲱' => '𐳱',
+ '𐲲' => '𐳲',
+ '𑢠' => '𑣀',
+ '𑢡' => '𑣁',
+ '𑢢' => '𑣂',
+ '𑢣' => '𑣃',
+ '𑢤' => '𑣄',
+ '𑢥' => '𑣅',
+ '𑢦' => '𑣆',
+ '𑢧' => '𑣇',
+ '𑢨' => '𑣈',
+ '𑢩' => '𑣉',
+ '𑢪' => '𑣊',
+ '𑢫' => '𑣋',
+ '𑢬' => '𑣌',
+ '𑢭' => '𑣍',
+ '𑢮' => '𑣎',
+ '𑢯' => '𑣏',
+ '𑢰' => '𑣐',
+ '𑢱' => '𑣑',
+ '𑢲' => '𑣒',
+ '𑢳' => '𑣓',
+ '𑢴' => '𑣔',
+ '𑢵' => '𑣕',
+ '𑢶' => '𑣖',
+ '𑢷' => '𑣗',
+ '𑢸' => '𑣘',
+ '𑢹' => '𑣙',
+ '𑢺' => '𑣚',
+ '𑢻' => '𑣛',
+ '𑢼' => '𑣜',
+ '𑢽' => '𑣝',
+ '𑢾' => '𑣞',
+ '𑢿' => '𑣟',
+ '𖹀' => '𖹠',
+ '𖹁' => '𖹡',
+ '𖹂' => '𖹢',
+ '𖹃' => '𖹣',
+ '𖹄' => '𖹤',
+ '𖹅' => '𖹥',
+ '𖹆' => '𖹦',
+ '𖹇' => '𖹧',
+ '𖹈' => '𖹨',
+ '𖹉' => '𖹩',
+ '𖹊' => '𖹪',
+ '𖹋' => '𖹫',
+ '𖹌' => '𖹬',
+ '𖹍' => '𖹭',
+ '𖹎' => '𖹮',
+ '𖹏' => '𖹯',
+ '𖹐' => '𖹰',
+ '𖹑' => '𖹱',
+ '𖹒' => '𖹲',
+ '𖹓' => '𖹳',
+ '𖹔' => '𖹴',
+ '𖹕' => '𖹵',
+ '𖹖' => '𖹶',
+ '𖹗' => '𖹷',
+ '𖹘' => '𖹸',
+ '𖹙' => '𖹹',
+ '𖹚' => '𖹺',
+ '𖹛' => '𖹻',
+ '𖹜' => '𖹼',
+ '𖹝' => '𖹽',
+ '𖹞' => '𖹾',
+ '𖹟' => '𖹿',
+ '𞤀' => '𞤢',
+ '𞤁' => '𞤣',
+ '𞤂' => '𞤤',
+ '𞤃' => '𞤥',
+ '𞤄' => '𞤦',
+ '𞤅' => '𞤧',
+ '𞤆' => '𞤨',
+ '𞤇' => '𞤩',
+ '𞤈' => '𞤪',
+ '𞤉' => '𞤫',
+ '𞤊' => '𞤬',
+ '𞤋' => '𞤭',
+ '𞤌' => '𞤮',
+ '𞤍' => '𞤯',
+ '𞤎' => '𞤰',
+ '𞤏' => '𞤱',
+ '𞤐' => '𞤲',
+ '𞤑' => '𞤳',
+ '𞤒' => '𞤴',
+ '𞤓' => '𞤵',
+ '𞤔' => '𞤶',
+ '𞤕' => '𞤷',
+ '𞤖' => '𞤸',
+ '𞤗' => '𞤹',
+ '𞤘' => '𞤺',
+ '𞤙' => '𞤻',
+ '𞤚' => '𞤼',
+ '𞤛' => '𞤽',
+ '𞤜' => '𞤾',
+ '𞤝' => '𞤿',
+ '𞤞' => '𞥀',
+ '𞤟' => '𞥁',
+ '𞤠' => '𞥂',
+ '𞤡' => '𞥃',
+);
diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php
new file mode 100644
index 000000000..2a8f6e73b
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php
@@ -0,0 +1,5 @@
+ 'A',
+ 'b' => 'B',
+ 'c' => 'C',
+ 'd' => 'D',
+ 'e' => 'E',
+ 'f' => 'F',
+ 'g' => 'G',
+ 'h' => 'H',
+ 'i' => 'I',
+ 'j' => 'J',
+ 'k' => 'K',
+ 'l' => 'L',
+ 'm' => 'M',
+ 'n' => 'N',
+ 'o' => 'O',
+ 'p' => 'P',
+ 'q' => 'Q',
+ 'r' => 'R',
+ 's' => 'S',
+ 't' => 'T',
+ 'u' => 'U',
+ 'v' => 'V',
+ 'w' => 'W',
+ 'x' => 'X',
+ 'y' => 'Y',
+ 'z' => 'Z',
+ 'µ' => 'Μ',
+ 'à' => 'À',
+ 'á' => 'Á',
+ 'â' => 'Â',
+ 'ã' => 'Ã',
+ 'ä' => 'Ä',
+ 'å' => 'Å',
+ 'æ' => 'Æ',
+ 'ç' => 'Ç',
+ 'è' => 'È',
+ 'é' => 'É',
+ 'ê' => 'Ê',
+ 'ë' => 'Ë',
+ 'ì' => 'Ì',
+ 'í' => 'Í',
+ 'î' => 'Î',
+ 'ï' => 'Ï',
+ 'ð' => 'Ð',
+ 'ñ' => 'Ñ',
+ 'ò' => 'Ò',
+ 'ó' => 'Ó',
+ 'ô' => 'Ô',
+ 'õ' => 'Õ',
+ 'ö' => 'Ö',
+ 'ø' => 'Ø',
+ 'ù' => 'Ù',
+ 'ú' => 'Ú',
+ 'û' => 'Û',
+ 'ü' => 'Ü',
+ 'ý' => 'Ý',
+ 'þ' => 'Þ',
+ 'ÿ' => 'Ÿ',
+ 'ā' => 'Ā',
+ 'ă' => 'Ă',
+ 'ą' => 'Ą',
+ 'ć' => 'Ć',
+ 'ĉ' => 'Ĉ',
+ 'ċ' => 'Ċ',
+ 'č' => 'Č',
+ 'ď' => 'Ď',
+ 'đ' => 'Đ',
+ 'ē' => 'Ē',
+ 'ĕ' => 'Ĕ',
+ 'ė' => 'Ė',
+ 'ę' => 'Ę',
+ 'ě' => 'Ě',
+ 'ĝ' => 'Ĝ',
+ 'ğ' => 'Ğ',
+ 'ġ' => 'Ġ',
+ 'ģ' => 'Ģ',
+ 'ĥ' => 'Ĥ',
+ 'ħ' => 'Ħ',
+ 'ĩ' => 'Ĩ',
+ 'ī' => 'Ī',
+ 'ĭ' => 'Ĭ',
+ 'į' => 'Į',
+ 'ı' => 'I',
+ 'ij' => 'IJ',
+ 'ĵ' => 'Ĵ',
+ 'ķ' => 'Ķ',
+ 'ĺ' => 'Ĺ',
+ 'ļ' => 'Ļ',
+ 'ľ' => 'Ľ',
+ 'ŀ' => 'Ŀ',
+ 'ł' => 'Ł',
+ 'ń' => 'Ń',
+ 'ņ' => 'Ņ',
+ 'ň' => 'Ň',
+ 'ŋ' => 'Ŋ',
+ 'ō' => 'Ō',
+ 'ŏ' => 'Ŏ',
+ 'ő' => 'Ő',
+ 'œ' => 'Œ',
+ 'ŕ' => 'Ŕ',
+ 'ŗ' => 'Ŗ',
+ 'ř' => 'Ř',
+ 'ś' => 'Ś',
+ 'ŝ' => 'Ŝ',
+ 'ş' => 'Ş',
+ 'š' => 'Š',
+ 'ţ' => 'Ţ',
+ 'ť' => 'Ť',
+ 'ŧ' => 'Ŧ',
+ 'ũ' => 'Ũ',
+ 'ū' => 'Ū',
+ 'ŭ' => 'Ŭ',
+ 'ů' => 'Ů',
+ 'ű' => 'Ű',
+ 'ų' => 'Ų',
+ 'ŵ' => 'Ŵ',
+ 'ŷ' => 'Ŷ',
+ 'ź' => 'Ź',
+ 'ż' => 'Ż',
+ 'ž' => 'Ž',
+ 'ſ' => 'S',
+ 'ƀ' => 'Ƀ',
+ 'ƃ' => 'Ƃ',
+ 'ƅ' => 'Ƅ',
+ 'ƈ' => 'Ƈ',
+ 'ƌ' => 'Ƌ',
+ 'ƒ' => 'Ƒ',
+ 'ƕ' => 'Ƕ',
+ 'ƙ' => 'Ƙ',
+ 'ƚ' => 'Ƚ',
+ 'ƞ' => 'Ƞ',
+ 'ơ' => 'Ơ',
+ 'ƣ' => 'Ƣ',
+ 'ƥ' => 'Ƥ',
+ 'ƨ' => 'Ƨ',
+ 'ƭ' => 'Ƭ',
+ 'ư' => 'Ư',
+ 'ƴ' => 'Ƴ',
+ 'ƶ' => 'Ƶ',
+ 'ƹ' => 'Ƹ',
+ 'ƽ' => 'Ƽ',
+ 'ƿ' => 'Ƿ',
+ 'Dž' => 'DŽ',
+ 'dž' => 'DŽ',
+ 'Lj' => 'LJ',
+ 'lj' => 'LJ',
+ 'Nj' => 'NJ',
+ 'nj' => 'NJ',
+ 'ǎ' => 'Ǎ',
+ 'ǐ' => 'Ǐ',
+ 'ǒ' => 'Ǒ',
+ 'ǔ' => 'Ǔ',
+ 'ǖ' => 'Ǖ',
+ 'ǘ' => 'Ǘ',
+ 'ǚ' => 'Ǚ',
+ 'ǜ' => 'Ǜ',
+ 'ǝ' => 'Ǝ',
+ 'ǟ' => 'Ǟ',
+ 'ǡ' => 'Ǡ',
+ 'ǣ' => 'Ǣ',
+ 'ǥ' => 'Ǥ',
+ 'ǧ' => 'Ǧ',
+ 'ǩ' => 'Ǩ',
+ 'ǫ' => 'Ǫ',
+ 'ǭ' => 'Ǭ',
+ 'ǯ' => 'Ǯ',
+ 'Dz' => 'DZ',
+ 'dz' => 'DZ',
+ 'ǵ' => 'Ǵ',
+ 'ǹ' => 'Ǹ',
+ 'ǻ' => 'Ǻ',
+ 'ǽ' => 'Ǽ',
+ 'ǿ' => 'Ǿ',
+ 'ȁ' => 'Ȁ',
+ 'ȃ' => 'Ȃ',
+ 'ȅ' => 'Ȅ',
+ 'ȇ' => 'Ȇ',
+ 'ȉ' => 'Ȉ',
+ 'ȋ' => 'Ȋ',
+ 'ȍ' => 'Ȍ',
+ 'ȏ' => 'Ȏ',
+ 'ȑ' => 'Ȑ',
+ 'ȓ' => 'Ȓ',
+ 'ȕ' => 'Ȕ',
+ 'ȗ' => 'Ȗ',
+ 'ș' => 'Ș',
+ 'ț' => 'Ț',
+ 'ȝ' => 'Ȝ',
+ 'ȟ' => 'Ȟ',
+ 'ȣ' => 'Ȣ',
+ 'ȥ' => 'Ȥ',
+ 'ȧ' => 'Ȧ',
+ 'ȩ' => 'Ȩ',
+ 'ȫ' => 'Ȫ',
+ 'ȭ' => 'Ȭ',
+ 'ȯ' => 'Ȯ',
+ 'ȱ' => 'Ȱ',
+ 'ȳ' => 'Ȳ',
+ 'ȼ' => 'Ȼ',
+ 'ȿ' => 'Ȿ',
+ 'ɀ' => 'Ɀ',
+ 'ɂ' => 'Ɂ',
+ 'ɇ' => 'Ɇ',
+ 'ɉ' => 'Ɉ',
+ 'ɋ' => 'Ɋ',
+ 'ɍ' => 'Ɍ',
+ 'ɏ' => 'Ɏ',
+ 'ɐ' => 'Ɐ',
+ 'ɑ' => 'Ɑ',
+ 'ɒ' => 'Ɒ',
+ 'ɓ' => 'Ɓ',
+ 'ɔ' => 'Ɔ',
+ 'ɖ' => 'Ɖ',
+ 'ɗ' => 'Ɗ',
+ 'ə' => 'Ə',
+ 'ɛ' => 'Ɛ',
+ 'ɜ' => 'Ɜ',
+ 'ɠ' => 'Ɠ',
+ 'ɡ' => 'Ɡ',
+ 'ɣ' => 'Ɣ',
+ 'ɥ' => 'Ɥ',
+ 'ɦ' => 'Ɦ',
+ 'ɨ' => 'Ɨ',
+ 'ɩ' => 'Ɩ',
+ 'ɪ' => 'Ɪ',
+ 'ɫ' => 'Ɫ',
+ 'ɬ' => 'Ɬ',
+ 'ɯ' => 'Ɯ',
+ 'ɱ' => 'Ɱ',
+ 'ɲ' => 'Ɲ',
+ 'ɵ' => 'Ɵ',
+ 'ɽ' => 'Ɽ',
+ 'ʀ' => 'Ʀ',
+ 'ʂ' => 'Ʂ',
+ 'ʃ' => 'Ʃ',
+ 'ʇ' => 'Ʇ',
+ 'ʈ' => 'Ʈ',
+ 'ʉ' => 'Ʉ',
+ 'ʊ' => 'Ʊ',
+ 'ʋ' => 'Ʋ',
+ 'ʌ' => 'Ʌ',
+ 'ʒ' => 'Ʒ',
+ 'ʝ' => 'Ʝ',
+ 'ʞ' => 'Ʞ',
+ 'ͅ' => 'Ι',
+ 'ͱ' => 'Ͱ',
+ 'ͳ' => 'Ͳ',
+ 'ͷ' => 'Ͷ',
+ 'ͻ' => 'Ͻ',
+ 'ͼ' => 'Ͼ',
+ 'ͽ' => 'Ͽ',
+ 'ά' => 'Ά',
+ 'έ' => 'Έ',
+ 'ή' => 'Ή',
+ 'ί' => 'Ί',
+ 'α' => 'Α',
+ 'β' => 'Β',
+ 'γ' => 'Γ',
+ 'δ' => 'Δ',
+ 'ε' => 'Ε',
+ 'ζ' => 'Ζ',
+ 'η' => 'Η',
+ 'θ' => 'Θ',
+ 'ι' => 'Ι',
+ 'κ' => 'Κ',
+ 'λ' => 'Λ',
+ 'μ' => 'Μ',
+ 'ν' => 'Ν',
+ 'ξ' => 'Ξ',
+ 'ο' => 'Ο',
+ 'π' => 'Π',
+ 'ρ' => 'Ρ',
+ 'ς' => 'Σ',
+ 'σ' => 'Σ',
+ 'τ' => 'Τ',
+ 'υ' => 'Υ',
+ 'φ' => 'Φ',
+ 'χ' => 'Χ',
+ 'ψ' => 'Ψ',
+ 'ω' => 'Ω',
+ 'ϊ' => 'Ϊ',
+ 'ϋ' => 'Ϋ',
+ 'ό' => 'Ό',
+ 'ύ' => 'Ύ',
+ 'ώ' => 'Ώ',
+ 'ϐ' => 'Β',
+ 'ϑ' => 'Θ',
+ 'ϕ' => 'Φ',
+ 'ϖ' => 'Π',
+ 'ϗ' => 'Ϗ',
+ 'ϙ' => 'Ϙ',
+ 'ϛ' => 'Ϛ',
+ 'ϝ' => 'Ϝ',
+ 'ϟ' => 'Ϟ',
+ 'ϡ' => 'Ϡ',
+ 'ϣ' => 'Ϣ',
+ 'ϥ' => 'Ϥ',
+ 'ϧ' => 'Ϧ',
+ 'ϩ' => 'Ϩ',
+ 'ϫ' => 'Ϫ',
+ 'ϭ' => 'Ϭ',
+ 'ϯ' => 'Ϯ',
+ 'ϰ' => 'Κ',
+ 'ϱ' => 'Ρ',
+ 'ϲ' => 'Ϲ',
+ 'ϳ' => 'Ϳ',
+ 'ϵ' => 'Ε',
+ 'ϸ' => 'Ϸ',
+ 'ϻ' => 'Ϻ',
+ 'а' => 'А',
+ 'б' => 'Б',
+ 'в' => 'В',
+ 'г' => 'Г',
+ 'д' => 'Д',
+ 'е' => 'Е',
+ 'ж' => 'Ж',
+ 'з' => 'З',
+ 'и' => 'И',
+ 'й' => 'Й',
+ 'к' => 'К',
+ 'л' => 'Л',
+ 'м' => 'М',
+ 'н' => 'Н',
+ 'о' => 'О',
+ 'п' => 'П',
+ 'р' => 'Р',
+ 'с' => 'С',
+ 'т' => 'Т',
+ 'у' => 'У',
+ 'ф' => 'Ф',
+ 'х' => 'Х',
+ 'ц' => 'Ц',
+ 'ч' => 'Ч',
+ 'ш' => 'Ш',
+ 'щ' => 'Щ',
+ 'ъ' => 'Ъ',
+ 'ы' => 'Ы',
+ 'ь' => 'Ь',
+ 'э' => 'Э',
+ 'ю' => 'Ю',
+ 'я' => 'Я',
+ 'ѐ' => 'Ѐ',
+ 'ё' => 'Ё',
+ 'ђ' => 'Ђ',
+ 'ѓ' => 'Ѓ',
+ 'є' => 'Є',
+ 'ѕ' => 'Ѕ',
+ 'і' => 'І',
+ 'ї' => 'Ї',
+ 'ј' => 'Ј',
+ 'љ' => 'Љ',
+ 'њ' => 'Њ',
+ 'ћ' => 'Ћ',
+ 'ќ' => 'Ќ',
+ 'ѝ' => 'Ѝ',
+ 'ў' => 'Ў',
+ 'џ' => 'Џ',
+ 'ѡ' => 'Ѡ',
+ 'ѣ' => 'Ѣ',
+ 'ѥ' => 'Ѥ',
+ 'ѧ' => 'Ѧ',
+ 'ѩ' => 'Ѩ',
+ 'ѫ' => 'Ѫ',
+ 'ѭ' => 'Ѭ',
+ 'ѯ' => 'Ѯ',
+ 'ѱ' => 'Ѱ',
+ 'ѳ' => 'Ѳ',
+ 'ѵ' => 'Ѵ',
+ 'ѷ' => 'Ѷ',
+ 'ѹ' => 'Ѹ',
+ 'ѻ' => 'Ѻ',
+ 'ѽ' => 'Ѽ',
+ 'ѿ' => 'Ѿ',
+ 'ҁ' => 'Ҁ',
+ 'ҋ' => 'Ҋ',
+ 'ҍ' => 'Ҍ',
+ 'ҏ' => 'Ҏ',
+ 'ґ' => 'Ґ',
+ 'ғ' => 'Ғ',
+ 'ҕ' => 'Ҕ',
+ 'җ' => 'Җ',
+ 'ҙ' => 'Ҙ',
+ 'қ' => 'Қ',
+ 'ҝ' => 'Ҝ',
+ 'ҟ' => 'Ҟ',
+ 'ҡ' => 'Ҡ',
+ 'ң' => 'Ң',
+ 'ҥ' => 'Ҥ',
+ 'ҧ' => 'Ҧ',
+ 'ҩ' => 'Ҩ',
+ 'ҫ' => 'Ҫ',
+ 'ҭ' => 'Ҭ',
+ 'ү' => 'Ү',
+ 'ұ' => 'Ұ',
+ 'ҳ' => 'Ҳ',
+ 'ҵ' => 'Ҵ',
+ 'ҷ' => 'Ҷ',
+ 'ҹ' => 'Ҹ',
+ 'һ' => 'Һ',
+ 'ҽ' => 'Ҽ',
+ 'ҿ' => 'Ҿ',
+ 'ӂ' => 'Ӂ',
+ 'ӄ' => 'Ӄ',
+ 'ӆ' => 'Ӆ',
+ 'ӈ' => 'Ӈ',
+ 'ӊ' => 'Ӊ',
+ 'ӌ' => 'Ӌ',
+ 'ӎ' => 'Ӎ',
+ 'ӏ' => 'Ӏ',
+ 'ӑ' => 'Ӑ',
+ 'ӓ' => 'Ӓ',
+ 'ӕ' => 'Ӕ',
+ 'ӗ' => 'Ӗ',
+ 'ә' => 'Ә',
+ 'ӛ' => 'Ӛ',
+ 'ӝ' => 'Ӝ',
+ 'ӟ' => 'Ӟ',
+ 'ӡ' => 'Ӡ',
+ 'ӣ' => 'Ӣ',
+ 'ӥ' => 'Ӥ',
+ 'ӧ' => 'Ӧ',
+ 'ө' => 'Ө',
+ 'ӫ' => 'Ӫ',
+ 'ӭ' => 'Ӭ',
+ 'ӯ' => 'Ӯ',
+ 'ӱ' => 'Ӱ',
+ 'ӳ' => 'Ӳ',
+ 'ӵ' => 'Ӵ',
+ 'ӷ' => 'Ӷ',
+ 'ӹ' => 'Ӹ',
+ 'ӻ' => 'Ӻ',
+ 'ӽ' => 'Ӽ',
+ 'ӿ' => 'Ӿ',
+ 'ԁ' => 'Ԁ',
+ 'ԃ' => 'Ԃ',
+ 'ԅ' => 'Ԅ',
+ 'ԇ' => 'Ԇ',
+ 'ԉ' => 'Ԉ',
+ 'ԋ' => 'Ԋ',
+ 'ԍ' => 'Ԍ',
+ 'ԏ' => 'Ԏ',
+ 'ԑ' => 'Ԑ',
+ 'ԓ' => 'Ԓ',
+ 'ԕ' => 'Ԕ',
+ 'ԗ' => 'Ԗ',
+ 'ԙ' => 'Ԙ',
+ 'ԛ' => 'Ԛ',
+ 'ԝ' => 'Ԝ',
+ 'ԟ' => 'Ԟ',
+ 'ԡ' => 'Ԡ',
+ 'ԣ' => 'Ԣ',
+ 'ԥ' => 'Ԥ',
+ 'ԧ' => 'Ԧ',
+ 'ԩ' => 'Ԩ',
+ 'ԫ' => 'Ԫ',
+ 'ԭ' => 'Ԭ',
+ 'ԯ' => 'Ԯ',
+ 'ա' => 'Ա',
+ 'բ' => 'Բ',
+ 'գ' => 'Գ',
+ 'դ' => 'Դ',
+ 'ե' => 'Ե',
+ 'զ' => 'Զ',
+ 'է' => 'Է',
+ 'ը' => 'Ը',
+ 'թ' => 'Թ',
+ 'ժ' => 'Ժ',
+ 'ի' => 'Ի',
+ 'լ' => 'Լ',
+ 'խ' => 'Խ',
+ 'ծ' => 'Ծ',
+ 'կ' => 'Կ',
+ 'հ' => 'Հ',
+ 'ձ' => 'Ձ',
+ 'ղ' => 'Ղ',
+ 'ճ' => 'Ճ',
+ 'մ' => 'Մ',
+ 'յ' => 'Յ',
+ 'ն' => 'Ն',
+ 'շ' => 'Շ',
+ 'ո' => 'Ո',
+ 'չ' => 'Չ',
+ 'պ' => 'Պ',
+ 'ջ' => 'Ջ',
+ 'ռ' => 'Ռ',
+ 'ս' => 'Ս',
+ 'վ' => 'Վ',
+ 'տ' => 'Տ',
+ 'ր' => 'Ր',
+ 'ց' => 'Ց',
+ 'ւ' => 'Ւ',
+ 'փ' => 'Փ',
+ 'ք' => 'Ք',
+ 'օ' => 'Օ',
+ 'ֆ' => 'Ֆ',
+ 'ა' => 'Ა',
+ 'ბ' => 'Ბ',
+ 'გ' => 'Გ',
+ 'დ' => 'Დ',
+ 'ე' => 'Ე',
+ 'ვ' => 'Ვ',
+ 'ზ' => 'Ზ',
+ 'თ' => 'Თ',
+ 'ი' => 'Ი',
+ 'კ' => 'Კ',
+ 'ლ' => 'Ლ',
+ 'მ' => 'Მ',
+ 'ნ' => 'Ნ',
+ 'ო' => 'Ო',
+ 'პ' => 'Პ',
+ 'ჟ' => 'Ჟ',
+ 'რ' => 'Რ',
+ 'ს' => 'Ს',
+ 'ტ' => 'Ტ',
+ 'უ' => 'Უ',
+ 'ფ' => 'Ფ',
+ 'ქ' => 'Ქ',
+ 'ღ' => 'Ღ',
+ 'ყ' => 'Ყ',
+ 'შ' => 'Შ',
+ 'ჩ' => 'Ჩ',
+ 'ც' => 'Ც',
+ 'ძ' => 'Ძ',
+ 'წ' => 'Წ',
+ 'ჭ' => 'Ჭ',
+ 'ხ' => 'Ხ',
+ 'ჯ' => 'Ჯ',
+ 'ჰ' => 'Ჰ',
+ 'ჱ' => 'Ჱ',
+ 'ჲ' => 'Ჲ',
+ 'ჳ' => 'Ჳ',
+ 'ჴ' => 'Ჴ',
+ 'ჵ' => 'Ჵ',
+ 'ჶ' => 'Ჶ',
+ 'ჷ' => 'Ჷ',
+ 'ჸ' => 'Ჸ',
+ 'ჹ' => 'Ჹ',
+ 'ჺ' => 'Ჺ',
+ 'ჽ' => 'Ჽ',
+ 'ჾ' => 'Ჾ',
+ 'ჿ' => 'Ჿ',
+ 'ᏸ' => 'Ᏸ',
+ 'ᏹ' => 'Ᏹ',
+ 'ᏺ' => 'Ᏺ',
+ 'ᏻ' => 'Ᏻ',
+ 'ᏼ' => 'Ᏼ',
+ 'ᏽ' => 'Ᏽ',
+ 'ᲀ' => 'В',
+ 'ᲁ' => 'Д',
+ 'ᲂ' => 'О',
+ 'ᲃ' => 'С',
+ 'ᲄ' => 'Т',
+ 'ᲅ' => 'Т',
+ 'ᲆ' => 'Ъ',
+ 'ᲇ' => 'Ѣ',
+ 'ᲈ' => 'Ꙋ',
+ 'ᵹ' => 'Ᵹ',
+ 'ᵽ' => 'Ᵽ',
+ 'ᶎ' => 'Ᶎ',
+ 'ḁ' => 'Ḁ',
+ 'ḃ' => 'Ḃ',
+ 'ḅ' => 'Ḅ',
+ 'ḇ' => 'Ḇ',
+ 'ḉ' => 'Ḉ',
+ 'ḋ' => 'Ḋ',
+ 'ḍ' => 'Ḍ',
+ 'ḏ' => 'Ḏ',
+ 'ḑ' => 'Ḑ',
+ 'ḓ' => 'Ḓ',
+ 'ḕ' => 'Ḕ',
+ 'ḗ' => 'Ḗ',
+ 'ḙ' => 'Ḙ',
+ 'ḛ' => 'Ḛ',
+ 'ḝ' => 'Ḝ',
+ 'ḟ' => 'Ḟ',
+ 'ḡ' => 'Ḡ',
+ 'ḣ' => 'Ḣ',
+ 'ḥ' => 'Ḥ',
+ 'ḧ' => 'Ḧ',
+ 'ḩ' => 'Ḩ',
+ 'ḫ' => 'Ḫ',
+ 'ḭ' => 'Ḭ',
+ 'ḯ' => 'Ḯ',
+ 'ḱ' => 'Ḱ',
+ 'ḳ' => 'Ḳ',
+ 'ḵ' => 'Ḵ',
+ 'ḷ' => 'Ḷ',
+ 'ḹ' => 'Ḹ',
+ 'ḻ' => 'Ḻ',
+ 'ḽ' => 'Ḽ',
+ 'ḿ' => 'Ḿ',
+ 'ṁ' => 'Ṁ',
+ 'ṃ' => 'Ṃ',
+ 'ṅ' => 'Ṅ',
+ 'ṇ' => 'Ṇ',
+ 'ṉ' => 'Ṉ',
+ 'ṋ' => 'Ṋ',
+ 'ṍ' => 'Ṍ',
+ 'ṏ' => 'Ṏ',
+ 'ṑ' => 'Ṑ',
+ 'ṓ' => 'Ṓ',
+ 'ṕ' => 'Ṕ',
+ 'ṗ' => 'Ṗ',
+ 'ṙ' => 'Ṙ',
+ 'ṛ' => 'Ṛ',
+ 'ṝ' => 'Ṝ',
+ 'ṟ' => 'Ṟ',
+ 'ṡ' => 'Ṡ',
+ 'ṣ' => 'Ṣ',
+ 'ṥ' => 'Ṥ',
+ 'ṧ' => 'Ṧ',
+ 'ṩ' => 'Ṩ',
+ 'ṫ' => 'Ṫ',
+ 'ṭ' => 'Ṭ',
+ 'ṯ' => 'Ṯ',
+ 'ṱ' => 'Ṱ',
+ 'ṳ' => 'Ṳ',
+ 'ṵ' => 'Ṵ',
+ 'ṷ' => 'Ṷ',
+ 'ṹ' => 'Ṹ',
+ 'ṻ' => 'Ṻ',
+ 'ṽ' => 'Ṽ',
+ 'ṿ' => 'Ṿ',
+ 'ẁ' => 'Ẁ',
+ 'ẃ' => 'Ẃ',
+ 'ẅ' => 'Ẅ',
+ 'ẇ' => 'Ẇ',
+ 'ẉ' => 'Ẉ',
+ 'ẋ' => 'Ẋ',
+ 'ẍ' => 'Ẍ',
+ 'ẏ' => 'Ẏ',
+ 'ẑ' => 'Ẑ',
+ 'ẓ' => 'Ẓ',
+ 'ẕ' => 'Ẕ',
+ 'ẛ' => 'Ṡ',
+ 'ạ' => 'Ạ',
+ 'ả' => 'Ả',
+ 'ấ' => 'Ấ',
+ 'ầ' => 'Ầ',
+ 'ẩ' => 'Ẩ',
+ 'ẫ' => 'Ẫ',
+ 'ậ' => 'Ậ',
+ 'ắ' => 'Ắ',
+ 'ằ' => 'Ằ',
+ 'ẳ' => 'Ẳ',
+ 'ẵ' => 'Ẵ',
+ 'ặ' => 'Ặ',
+ 'ẹ' => 'Ẹ',
+ 'ẻ' => 'Ẻ',
+ 'ẽ' => 'Ẽ',
+ 'ế' => 'Ế',
+ 'ề' => 'Ề',
+ 'ể' => 'Ể',
+ 'ễ' => 'Ễ',
+ 'ệ' => 'Ệ',
+ 'ỉ' => 'Ỉ',
+ 'ị' => 'Ị',
+ 'ọ' => 'Ọ',
+ 'ỏ' => 'Ỏ',
+ 'ố' => 'Ố',
+ 'ồ' => 'Ồ',
+ 'ổ' => 'Ổ',
+ 'ỗ' => 'Ỗ',
+ 'ộ' => 'Ộ',
+ 'ớ' => 'Ớ',
+ 'ờ' => 'Ờ',
+ 'ở' => 'Ở',
+ 'ỡ' => 'Ỡ',
+ 'ợ' => 'Ợ',
+ 'ụ' => 'Ụ',
+ 'ủ' => 'Ủ',
+ 'ứ' => 'Ứ',
+ 'ừ' => 'Ừ',
+ 'ử' => 'Ử',
+ 'ữ' => 'Ữ',
+ 'ự' => 'Ự',
+ 'ỳ' => 'Ỳ',
+ 'ỵ' => 'Ỵ',
+ 'ỷ' => 'Ỷ',
+ 'ỹ' => 'Ỹ',
+ 'ỻ' => 'Ỻ',
+ 'ỽ' => 'Ỽ',
+ 'ỿ' => 'Ỿ',
+ 'ἀ' => 'Ἀ',
+ 'ἁ' => 'Ἁ',
+ 'ἂ' => 'Ἂ',
+ 'ἃ' => 'Ἃ',
+ 'ἄ' => 'Ἄ',
+ 'ἅ' => 'Ἅ',
+ 'ἆ' => 'Ἆ',
+ 'ἇ' => 'Ἇ',
+ 'ἐ' => 'Ἐ',
+ 'ἑ' => 'Ἑ',
+ 'ἒ' => 'Ἒ',
+ 'ἓ' => 'Ἓ',
+ 'ἔ' => 'Ἔ',
+ 'ἕ' => 'Ἕ',
+ 'ἠ' => 'Ἠ',
+ 'ἡ' => 'Ἡ',
+ 'ἢ' => 'Ἢ',
+ 'ἣ' => 'Ἣ',
+ 'ἤ' => 'Ἤ',
+ 'ἥ' => 'Ἥ',
+ 'ἦ' => 'Ἦ',
+ 'ἧ' => 'Ἧ',
+ 'ἰ' => 'Ἰ',
+ 'ἱ' => 'Ἱ',
+ 'ἲ' => 'Ἲ',
+ 'ἳ' => 'Ἳ',
+ 'ἴ' => 'Ἴ',
+ 'ἵ' => 'Ἵ',
+ 'ἶ' => 'Ἶ',
+ 'ἷ' => 'Ἷ',
+ 'ὀ' => 'Ὀ',
+ 'ὁ' => 'Ὁ',
+ 'ὂ' => 'Ὂ',
+ 'ὃ' => 'Ὃ',
+ 'ὄ' => 'Ὄ',
+ 'ὅ' => 'Ὅ',
+ 'ὑ' => 'Ὑ',
+ 'ὓ' => 'Ὓ',
+ 'ὕ' => 'Ὕ',
+ 'ὗ' => 'Ὗ',
+ 'ὠ' => 'Ὠ',
+ 'ὡ' => 'Ὡ',
+ 'ὢ' => 'Ὢ',
+ 'ὣ' => 'Ὣ',
+ 'ὤ' => 'Ὤ',
+ 'ὥ' => 'Ὥ',
+ 'ὦ' => 'Ὦ',
+ 'ὧ' => 'Ὧ',
+ 'ὰ' => 'Ὰ',
+ 'ά' => 'Ά',
+ 'ὲ' => 'Ὲ',
+ 'έ' => 'Έ',
+ 'ὴ' => 'Ὴ',
+ 'ή' => 'Ή',
+ 'ὶ' => 'Ὶ',
+ 'ί' => 'Ί',
+ 'ὸ' => 'Ὸ',
+ 'ό' => 'Ό',
+ 'ὺ' => 'Ὺ',
+ 'ύ' => 'Ύ',
+ 'ὼ' => 'Ὼ',
+ 'ώ' => 'Ώ',
+ 'ᾀ' => 'ἈΙ',
+ 'ᾁ' => 'ἉΙ',
+ 'ᾂ' => 'ἊΙ',
+ 'ᾃ' => 'ἋΙ',
+ 'ᾄ' => 'ἌΙ',
+ 'ᾅ' => 'ἍΙ',
+ 'ᾆ' => 'ἎΙ',
+ 'ᾇ' => 'ἏΙ',
+ 'ᾐ' => 'ἨΙ',
+ 'ᾑ' => 'ἩΙ',
+ 'ᾒ' => 'ἪΙ',
+ 'ᾓ' => 'ἫΙ',
+ 'ᾔ' => 'ἬΙ',
+ 'ᾕ' => 'ἭΙ',
+ 'ᾖ' => 'ἮΙ',
+ 'ᾗ' => 'ἯΙ',
+ 'ᾠ' => 'ὨΙ',
+ 'ᾡ' => 'ὩΙ',
+ 'ᾢ' => 'ὪΙ',
+ 'ᾣ' => 'ὫΙ',
+ 'ᾤ' => 'ὬΙ',
+ 'ᾥ' => 'ὭΙ',
+ 'ᾦ' => 'ὮΙ',
+ 'ᾧ' => 'ὯΙ',
+ 'ᾰ' => 'Ᾰ',
+ 'ᾱ' => 'Ᾱ',
+ 'ᾳ' => 'ΑΙ',
+ 'ι' => 'Ι',
+ 'ῃ' => 'ΗΙ',
+ 'ῐ' => 'Ῐ',
+ 'ῑ' => 'Ῑ',
+ 'ῠ' => 'Ῠ',
+ 'ῡ' => 'Ῡ',
+ 'ῥ' => 'Ῥ',
+ 'ῳ' => 'ΩΙ',
+ 'ⅎ' => 'Ⅎ',
+ 'ⅰ' => 'Ⅰ',
+ 'ⅱ' => 'Ⅱ',
+ 'ⅲ' => 'Ⅲ',
+ 'ⅳ' => 'Ⅳ',
+ 'ⅴ' => 'Ⅴ',
+ 'ⅵ' => 'Ⅵ',
+ 'ⅶ' => 'Ⅶ',
+ 'ⅷ' => 'Ⅷ',
+ 'ⅸ' => 'Ⅸ',
+ 'ⅹ' => 'Ⅹ',
+ 'ⅺ' => 'Ⅺ',
+ 'ⅻ' => 'Ⅻ',
+ 'ⅼ' => 'Ⅼ',
+ 'ⅽ' => 'Ⅽ',
+ 'ⅾ' => 'Ⅾ',
+ 'ⅿ' => 'Ⅿ',
+ 'ↄ' => 'Ↄ',
+ 'ⓐ' => 'Ⓐ',
+ 'ⓑ' => 'Ⓑ',
+ 'ⓒ' => 'Ⓒ',
+ 'ⓓ' => 'Ⓓ',
+ 'ⓔ' => 'Ⓔ',
+ 'ⓕ' => 'Ⓕ',
+ 'ⓖ' => 'Ⓖ',
+ 'ⓗ' => 'Ⓗ',
+ 'ⓘ' => 'Ⓘ',
+ 'ⓙ' => 'Ⓙ',
+ 'ⓚ' => 'Ⓚ',
+ 'ⓛ' => 'Ⓛ',
+ 'ⓜ' => 'Ⓜ',
+ 'ⓝ' => 'Ⓝ',
+ 'ⓞ' => 'Ⓞ',
+ 'ⓟ' => 'Ⓟ',
+ 'ⓠ' => 'Ⓠ',
+ 'ⓡ' => 'Ⓡ',
+ 'ⓢ' => 'Ⓢ',
+ 'ⓣ' => 'Ⓣ',
+ 'ⓤ' => 'Ⓤ',
+ 'ⓥ' => 'Ⓥ',
+ 'ⓦ' => 'Ⓦ',
+ 'ⓧ' => 'Ⓧ',
+ 'ⓨ' => 'Ⓨ',
+ 'ⓩ' => 'Ⓩ',
+ 'ⰰ' => 'Ⰰ',
+ 'ⰱ' => 'Ⰱ',
+ 'ⰲ' => 'Ⰲ',
+ 'ⰳ' => 'Ⰳ',
+ 'ⰴ' => 'Ⰴ',
+ 'ⰵ' => 'Ⰵ',
+ 'ⰶ' => 'Ⰶ',
+ 'ⰷ' => 'Ⰷ',
+ 'ⰸ' => 'Ⰸ',
+ 'ⰹ' => 'Ⰹ',
+ 'ⰺ' => 'Ⰺ',
+ 'ⰻ' => 'Ⰻ',
+ 'ⰼ' => 'Ⰼ',
+ 'ⰽ' => 'Ⰽ',
+ 'ⰾ' => 'Ⰾ',
+ 'ⰿ' => 'Ⰿ',
+ 'ⱀ' => 'Ⱀ',
+ 'ⱁ' => 'Ⱁ',
+ 'ⱂ' => 'Ⱂ',
+ 'ⱃ' => 'Ⱃ',
+ 'ⱄ' => 'Ⱄ',
+ 'ⱅ' => 'Ⱅ',
+ 'ⱆ' => 'Ⱆ',
+ 'ⱇ' => 'Ⱇ',
+ 'ⱈ' => 'Ⱈ',
+ 'ⱉ' => 'Ⱉ',
+ 'ⱊ' => 'Ⱊ',
+ 'ⱋ' => 'Ⱋ',
+ 'ⱌ' => 'Ⱌ',
+ 'ⱍ' => 'Ⱍ',
+ 'ⱎ' => 'Ⱎ',
+ 'ⱏ' => 'Ⱏ',
+ 'ⱐ' => 'Ⱐ',
+ 'ⱑ' => 'Ⱑ',
+ 'ⱒ' => 'Ⱒ',
+ 'ⱓ' => 'Ⱓ',
+ 'ⱔ' => 'Ⱔ',
+ 'ⱕ' => 'Ⱕ',
+ 'ⱖ' => 'Ⱖ',
+ 'ⱗ' => 'Ⱗ',
+ 'ⱘ' => 'Ⱘ',
+ 'ⱙ' => 'Ⱙ',
+ 'ⱚ' => 'Ⱚ',
+ 'ⱛ' => 'Ⱛ',
+ 'ⱜ' => 'Ⱜ',
+ 'ⱝ' => 'Ⱝ',
+ 'ⱞ' => 'Ⱞ',
+ 'ⱡ' => 'Ⱡ',
+ 'ⱥ' => 'Ⱥ',
+ 'ⱦ' => 'Ⱦ',
+ 'ⱨ' => 'Ⱨ',
+ 'ⱪ' => 'Ⱪ',
+ 'ⱬ' => 'Ⱬ',
+ 'ⱳ' => 'Ⱳ',
+ 'ⱶ' => 'Ⱶ',
+ 'ⲁ' => 'Ⲁ',
+ 'ⲃ' => 'Ⲃ',
+ 'ⲅ' => 'Ⲅ',
+ 'ⲇ' => 'Ⲇ',
+ 'ⲉ' => 'Ⲉ',
+ 'ⲋ' => 'Ⲋ',
+ 'ⲍ' => 'Ⲍ',
+ 'ⲏ' => 'Ⲏ',
+ 'ⲑ' => 'Ⲑ',
+ 'ⲓ' => 'Ⲓ',
+ 'ⲕ' => 'Ⲕ',
+ 'ⲗ' => 'Ⲗ',
+ 'ⲙ' => 'Ⲙ',
+ 'ⲛ' => 'Ⲛ',
+ 'ⲝ' => 'Ⲝ',
+ 'ⲟ' => 'Ⲟ',
+ 'ⲡ' => 'Ⲡ',
+ 'ⲣ' => 'Ⲣ',
+ 'ⲥ' => 'Ⲥ',
+ 'ⲧ' => 'Ⲧ',
+ 'ⲩ' => 'Ⲩ',
+ 'ⲫ' => 'Ⲫ',
+ 'ⲭ' => 'Ⲭ',
+ 'ⲯ' => 'Ⲯ',
+ 'ⲱ' => 'Ⲱ',
+ 'ⲳ' => 'Ⲳ',
+ 'ⲵ' => 'Ⲵ',
+ 'ⲷ' => 'Ⲷ',
+ 'ⲹ' => 'Ⲹ',
+ 'ⲻ' => 'Ⲻ',
+ 'ⲽ' => 'Ⲽ',
+ 'ⲿ' => 'Ⲿ',
+ 'ⳁ' => 'Ⳁ',
+ 'ⳃ' => 'Ⳃ',
+ 'ⳅ' => 'Ⳅ',
+ 'ⳇ' => 'Ⳇ',
+ 'ⳉ' => 'Ⳉ',
+ 'ⳋ' => 'Ⳋ',
+ 'ⳍ' => 'Ⳍ',
+ 'ⳏ' => 'Ⳏ',
+ 'ⳑ' => 'Ⳑ',
+ 'ⳓ' => 'Ⳓ',
+ 'ⳕ' => 'Ⳕ',
+ 'ⳗ' => 'Ⳗ',
+ 'ⳙ' => 'Ⳙ',
+ 'ⳛ' => 'Ⳛ',
+ 'ⳝ' => 'Ⳝ',
+ 'ⳟ' => 'Ⳟ',
+ 'ⳡ' => 'Ⳡ',
+ 'ⳣ' => 'Ⳣ',
+ 'ⳬ' => 'Ⳬ',
+ 'ⳮ' => 'Ⳮ',
+ 'ⳳ' => 'Ⳳ',
+ 'ⴀ' => 'Ⴀ',
+ 'ⴁ' => 'Ⴁ',
+ 'ⴂ' => 'Ⴂ',
+ 'ⴃ' => 'Ⴃ',
+ 'ⴄ' => 'Ⴄ',
+ 'ⴅ' => 'Ⴅ',
+ 'ⴆ' => 'Ⴆ',
+ 'ⴇ' => 'Ⴇ',
+ 'ⴈ' => 'Ⴈ',
+ 'ⴉ' => 'Ⴉ',
+ 'ⴊ' => 'Ⴊ',
+ 'ⴋ' => 'Ⴋ',
+ 'ⴌ' => 'Ⴌ',
+ 'ⴍ' => 'Ⴍ',
+ 'ⴎ' => 'Ⴎ',
+ 'ⴏ' => 'Ⴏ',
+ 'ⴐ' => 'Ⴐ',
+ 'ⴑ' => 'Ⴑ',
+ 'ⴒ' => 'Ⴒ',
+ 'ⴓ' => 'Ⴓ',
+ 'ⴔ' => 'Ⴔ',
+ 'ⴕ' => 'Ⴕ',
+ 'ⴖ' => 'Ⴖ',
+ 'ⴗ' => 'Ⴗ',
+ 'ⴘ' => 'Ⴘ',
+ 'ⴙ' => 'Ⴙ',
+ 'ⴚ' => 'Ⴚ',
+ 'ⴛ' => 'Ⴛ',
+ 'ⴜ' => 'Ⴜ',
+ 'ⴝ' => 'Ⴝ',
+ 'ⴞ' => 'Ⴞ',
+ 'ⴟ' => 'Ⴟ',
+ 'ⴠ' => 'Ⴠ',
+ 'ⴡ' => 'Ⴡ',
+ 'ⴢ' => 'Ⴢ',
+ 'ⴣ' => 'Ⴣ',
+ 'ⴤ' => 'Ⴤ',
+ 'ⴥ' => 'Ⴥ',
+ 'ⴧ' => 'Ⴧ',
+ 'ⴭ' => 'Ⴭ',
+ 'ꙁ' => 'Ꙁ',
+ 'ꙃ' => 'Ꙃ',
+ 'ꙅ' => 'Ꙅ',
+ 'ꙇ' => 'Ꙇ',
+ 'ꙉ' => 'Ꙉ',
+ 'ꙋ' => 'Ꙋ',
+ 'ꙍ' => 'Ꙍ',
+ 'ꙏ' => 'Ꙏ',
+ 'ꙑ' => 'Ꙑ',
+ 'ꙓ' => 'Ꙓ',
+ 'ꙕ' => 'Ꙕ',
+ 'ꙗ' => 'Ꙗ',
+ 'ꙙ' => 'Ꙙ',
+ 'ꙛ' => 'Ꙛ',
+ 'ꙝ' => 'Ꙝ',
+ 'ꙟ' => 'Ꙟ',
+ 'ꙡ' => 'Ꙡ',
+ 'ꙣ' => 'Ꙣ',
+ 'ꙥ' => 'Ꙥ',
+ 'ꙧ' => 'Ꙧ',
+ 'ꙩ' => 'Ꙩ',
+ 'ꙫ' => 'Ꙫ',
+ 'ꙭ' => 'Ꙭ',
+ 'ꚁ' => 'Ꚁ',
+ 'ꚃ' => 'Ꚃ',
+ 'ꚅ' => 'Ꚅ',
+ 'ꚇ' => 'Ꚇ',
+ 'ꚉ' => 'Ꚉ',
+ 'ꚋ' => 'Ꚋ',
+ 'ꚍ' => 'Ꚍ',
+ 'ꚏ' => 'Ꚏ',
+ 'ꚑ' => 'Ꚑ',
+ 'ꚓ' => 'Ꚓ',
+ 'ꚕ' => 'Ꚕ',
+ 'ꚗ' => 'Ꚗ',
+ 'ꚙ' => 'Ꚙ',
+ 'ꚛ' => 'Ꚛ',
+ 'ꜣ' => 'Ꜣ',
+ 'ꜥ' => 'Ꜥ',
+ 'ꜧ' => 'Ꜧ',
+ 'ꜩ' => 'Ꜩ',
+ 'ꜫ' => 'Ꜫ',
+ 'ꜭ' => 'Ꜭ',
+ 'ꜯ' => 'Ꜯ',
+ 'ꜳ' => 'Ꜳ',
+ 'ꜵ' => 'Ꜵ',
+ 'ꜷ' => 'Ꜷ',
+ 'ꜹ' => 'Ꜹ',
+ 'ꜻ' => 'Ꜻ',
+ 'ꜽ' => 'Ꜽ',
+ 'ꜿ' => 'Ꜿ',
+ 'ꝁ' => 'Ꝁ',
+ 'ꝃ' => 'Ꝃ',
+ 'ꝅ' => 'Ꝅ',
+ 'ꝇ' => 'Ꝇ',
+ 'ꝉ' => 'Ꝉ',
+ 'ꝋ' => 'Ꝋ',
+ 'ꝍ' => 'Ꝍ',
+ 'ꝏ' => 'Ꝏ',
+ 'ꝑ' => 'Ꝑ',
+ 'ꝓ' => 'Ꝓ',
+ 'ꝕ' => 'Ꝕ',
+ 'ꝗ' => 'Ꝗ',
+ 'ꝙ' => 'Ꝙ',
+ 'ꝛ' => 'Ꝛ',
+ 'ꝝ' => 'Ꝝ',
+ 'ꝟ' => 'Ꝟ',
+ 'ꝡ' => 'Ꝡ',
+ 'ꝣ' => 'Ꝣ',
+ 'ꝥ' => 'Ꝥ',
+ 'ꝧ' => 'Ꝧ',
+ 'ꝩ' => 'Ꝩ',
+ 'ꝫ' => 'Ꝫ',
+ 'ꝭ' => 'Ꝭ',
+ 'ꝯ' => 'Ꝯ',
+ 'ꝺ' => 'Ꝺ',
+ 'ꝼ' => 'Ꝼ',
+ 'ꝿ' => 'Ꝿ',
+ 'ꞁ' => 'Ꞁ',
+ 'ꞃ' => 'Ꞃ',
+ 'ꞅ' => 'Ꞅ',
+ 'ꞇ' => 'Ꞇ',
+ 'ꞌ' => 'Ꞌ',
+ 'ꞑ' => 'Ꞑ',
+ 'ꞓ' => 'Ꞓ',
+ 'ꞔ' => 'Ꞔ',
+ 'ꞗ' => 'Ꞗ',
+ 'ꞙ' => 'Ꞙ',
+ 'ꞛ' => 'Ꞛ',
+ 'ꞝ' => 'Ꞝ',
+ 'ꞟ' => 'Ꞟ',
+ 'ꞡ' => 'Ꞡ',
+ 'ꞣ' => 'Ꞣ',
+ 'ꞥ' => 'Ꞥ',
+ 'ꞧ' => 'Ꞧ',
+ 'ꞩ' => 'Ꞩ',
+ 'ꞵ' => 'Ꞵ',
+ 'ꞷ' => 'Ꞷ',
+ 'ꞹ' => 'Ꞹ',
+ 'ꞻ' => 'Ꞻ',
+ 'ꞽ' => 'Ꞽ',
+ 'ꞿ' => 'Ꞿ',
+ 'ꟃ' => 'Ꟃ',
+ 'ꟈ' => 'Ꟈ',
+ 'ꟊ' => 'Ꟊ',
+ 'ꟶ' => 'Ꟶ',
+ 'ꭓ' => 'Ꭓ',
+ 'ꭰ' => 'Ꭰ',
+ 'ꭱ' => 'Ꭱ',
+ 'ꭲ' => 'Ꭲ',
+ 'ꭳ' => 'Ꭳ',
+ 'ꭴ' => 'Ꭴ',
+ 'ꭵ' => 'Ꭵ',
+ 'ꭶ' => 'Ꭶ',
+ 'ꭷ' => 'Ꭷ',
+ 'ꭸ' => 'Ꭸ',
+ 'ꭹ' => 'Ꭹ',
+ 'ꭺ' => 'Ꭺ',
+ 'ꭻ' => 'Ꭻ',
+ 'ꭼ' => 'Ꭼ',
+ 'ꭽ' => 'Ꭽ',
+ 'ꭾ' => 'Ꭾ',
+ 'ꭿ' => 'Ꭿ',
+ 'ꮀ' => 'Ꮀ',
+ 'ꮁ' => 'Ꮁ',
+ 'ꮂ' => 'Ꮂ',
+ 'ꮃ' => 'Ꮃ',
+ 'ꮄ' => 'Ꮄ',
+ 'ꮅ' => 'Ꮅ',
+ 'ꮆ' => 'Ꮆ',
+ 'ꮇ' => 'Ꮇ',
+ 'ꮈ' => 'Ꮈ',
+ 'ꮉ' => 'Ꮉ',
+ 'ꮊ' => 'Ꮊ',
+ 'ꮋ' => 'Ꮋ',
+ 'ꮌ' => 'Ꮌ',
+ 'ꮍ' => 'Ꮍ',
+ 'ꮎ' => 'Ꮎ',
+ 'ꮏ' => 'Ꮏ',
+ 'ꮐ' => 'Ꮐ',
+ 'ꮑ' => 'Ꮑ',
+ 'ꮒ' => 'Ꮒ',
+ 'ꮓ' => 'Ꮓ',
+ 'ꮔ' => 'Ꮔ',
+ 'ꮕ' => 'Ꮕ',
+ 'ꮖ' => 'Ꮖ',
+ 'ꮗ' => 'Ꮗ',
+ 'ꮘ' => 'Ꮘ',
+ 'ꮙ' => 'Ꮙ',
+ 'ꮚ' => 'Ꮚ',
+ 'ꮛ' => 'Ꮛ',
+ 'ꮜ' => 'Ꮜ',
+ 'ꮝ' => 'Ꮝ',
+ 'ꮞ' => 'Ꮞ',
+ 'ꮟ' => 'Ꮟ',
+ 'ꮠ' => 'Ꮠ',
+ 'ꮡ' => 'Ꮡ',
+ 'ꮢ' => 'Ꮢ',
+ 'ꮣ' => 'Ꮣ',
+ 'ꮤ' => 'Ꮤ',
+ 'ꮥ' => 'Ꮥ',
+ 'ꮦ' => 'Ꮦ',
+ 'ꮧ' => 'Ꮧ',
+ 'ꮨ' => 'Ꮨ',
+ 'ꮩ' => 'Ꮩ',
+ 'ꮪ' => 'Ꮪ',
+ 'ꮫ' => 'Ꮫ',
+ 'ꮬ' => 'Ꮬ',
+ 'ꮭ' => 'Ꮭ',
+ 'ꮮ' => 'Ꮮ',
+ 'ꮯ' => 'Ꮯ',
+ 'ꮰ' => 'Ꮰ',
+ 'ꮱ' => 'Ꮱ',
+ 'ꮲ' => 'Ꮲ',
+ 'ꮳ' => 'Ꮳ',
+ 'ꮴ' => 'Ꮴ',
+ 'ꮵ' => 'Ꮵ',
+ 'ꮶ' => 'Ꮶ',
+ 'ꮷ' => 'Ꮷ',
+ 'ꮸ' => 'Ꮸ',
+ 'ꮹ' => 'Ꮹ',
+ 'ꮺ' => 'Ꮺ',
+ 'ꮻ' => 'Ꮻ',
+ 'ꮼ' => 'Ꮼ',
+ 'ꮽ' => 'Ꮽ',
+ 'ꮾ' => 'Ꮾ',
+ 'ꮿ' => 'Ꮿ',
+ 'a' => 'A',
+ 'b' => 'B',
+ 'c' => 'C',
+ 'd' => 'D',
+ 'e' => 'E',
+ 'f' => 'F',
+ 'g' => 'G',
+ 'h' => 'H',
+ 'i' => 'I',
+ 'j' => 'J',
+ 'k' => 'K',
+ 'l' => 'L',
+ 'm' => 'M',
+ 'n' => 'N',
+ 'o' => 'O',
+ 'p' => 'P',
+ 'q' => 'Q',
+ 'r' => 'R',
+ 's' => 'S',
+ 't' => 'T',
+ 'u' => 'U',
+ 'v' => 'V',
+ 'w' => 'W',
+ 'x' => 'X',
+ 'y' => 'Y',
+ 'z' => 'Z',
+ '𐐨' => '𐐀',
+ '𐐩' => '𐐁',
+ '𐐪' => '𐐂',
+ '𐐫' => '𐐃',
+ '𐐬' => '𐐄',
+ '𐐭' => '𐐅',
+ '𐐮' => '𐐆',
+ '𐐯' => '𐐇',
+ '𐐰' => '𐐈',
+ '𐐱' => '𐐉',
+ '𐐲' => '𐐊',
+ '𐐳' => '𐐋',
+ '𐐴' => '𐐌',
+ '𐐵' => '𐐍',
+ '𐐶' => '𐐎',
+ '𐐷' => '𐐏',
+ '𐐸' => '𐐐',
+ '𐐹' => '𐐑',
+ '𐐺' => '𐐒',
+ '𐐻' => '𐐓',
+ '𐐼' => '𐐔',
+ '𐐽' => '𐐕',
+ '𐐾' => '𐐖',
+ '𐐿' => '𐐗',
+ '𐑀' => '𐐘',
+ '𐑁' => '𐐙',
+ '𐑂' => '𐐚',
+ '𐑃' => '𐐛',
+ '𐑄' => '𐐜',
+ '𐑅' => '𐐝',
+ '𐑆' => '𐐞',
+ '𐑇' => '𐐟',
+ '𐑈' => '𐐠',
+ '𐑉' => '𐐡',
+ '𐑊' => '𐐢',
+ '𐑋' => '𐐣',
+ '𐑌' => '𐐤',
+ '𐑍' => '𐐥',
+ '𐑎' => '𐐦',
+ '𐑏' => '𐐧',
+ '𐓘' => '𐒰',
+ '𐓙' => '𐒱',
+ '𐓚' => '𐒲',
+ '𐓛' => '𐒳',
+ '𐓜' => '𐒴',
+ '𐓝' => '𐒵',
+ '𐓞' => '𐒶',
+ '𐓟' => '𐒷',
+ '𐓠' => '𐒸',
+ '𐓡' => '𐒹',
+ '𐓢' => '𐒺',
+ '𐓣' => '𐒻',
+ '𐓤' => '𐒼',
+ '𐓥' => '𐒽',
+ '𐓦' => '𐒾',
+ '𐓧' => '𐒿',
+ '𐓨' => '𐓀',
+ '𐓩' => '𐓁',
+ '𐓪' => '𐓂',
+ '𐓫' => '𐓃',
+ '𐓬' => '𐓄',
+ '𐓭' => '𐓅',
+ '𐓮' => '𐓆',
+ '𐓯' => '𐓇',
+ '𐓰' => '𐓈',
+ '𐓱' => '𐓉',
+ '𐓲' => '𐓊',
+ '𐓳' => '𐓋',
+ '𐓴' => '𐓌',
+ '𐓵' => '𐓍',
+ '𐓶' => '𐓎',
+ '𐓷' => '𐓏',
+ '𐓸' => '𐓐',
+ '𐓹' => '𐓑',
+ '𐓺' => '𐓒',
+ '𐓻' => '𐓓',
+ '𐳀' => '𐲀',
+ '𐳁' => '𐲁',
+ '𐳂' => '𐲂',
+ '𐳃' => '𐲃',
+ '𐳄' => '𐲄',
+ '𐳅' => '𐲅',
+ '𐳆' => '𐲆',
+ '𐳇' => '𐲇',
+ '𐳈' => '𐲈',
+ '𐳉' => '𐲉',
+ '𐳊' => '𐲊',
+ '𐳋' => '𐲋',
+ '𐳌' => '𐲌',
+ '𐳍' => '𐲍',
+ '𐳎' => '𐲎',
+ '𐳏' => '𐲏',
+ '𐳐' => '𐲐',
+ '𐳑' => '𐲑',
+ '𐳒' => '𐲒',
+ '𐳓' => '𐲓',
+ '𐳔' => '𐲔',
+ '𐳕' => '𐲕',
+ '𐳖' => '𐲖',
+ '𐳗' => '𐲗',
+ '𐳘' => '𐲘',
+ '𐳙' => '𐲙',
+ '𐳚' => '𐲚',
+ '𐳛' => '𐲛',
+ '𐳜' => '𐲜',
+ '𐳝' => '𐲝',
+ '𐳞' => '𐲞',
+ '𐳟' => '𐲟',
+ '𐳠' => '𐲠',
+ '𐳡' => '𐲡',
+ '𐳢' => '𐲢',
+ '𐳣' => '𐲣',
+ '𐳤' => '𐲤',
+ '𐳥' => '𐲥',
+ '𐳦' => '𐲦',
+ '𐳧' => '𐲧',
+ '𐳨' => '𐲨',
+ '𐳩' => '𐲩',
+ '𐳪' => '𐲪',
+ '𐳫' => '𐲫',
+ '𐳬' => '𐲬',
+ '𐳭' => '𐲭',
+ '𐳮' => '𐲮',
+ '𐳯' => '𐲯',
+ '𐳰' => '𐲰',
+ '𐳱' => '𐲱',
+ '𐳲' => '𐲲',
+ '𑣀' => '𑢠',
+ '𑣁' => '𑢡',
+ '𑣂' => '𑢢',
+ '𑣃' => '𑢣',
+ '𑣄' => '𑢤',
+ '𑣅' => '𑢥',
+ '𑣆' => '𑢦',
+ '𑣇' => '𑢧',
+ '𑣈' => '𑢨',
+ '𑣉' => '𑢩',
+ '𑣊' => '𑢪',
+ '𑣋' => '𑢫',
+ '𑣌' => '𑢬',
+ '𑣍' => '𑢭',
+ '𑣎' => '𑢮',
+ '𑣏' => '𑢯',
+ '𑣐' => '𑢰',
+ '𑣑' => '𑢱',
+ '𑣒' => '𑢲',
+ '𑣓' => '𑢳',
+ '𑣔' => '𑢴',
+ '𑣕' => '𑢵',
+ '𑣖' => '𑢶',
+ '𑣗' => '𑢷',
+ '𑣘' => '𑢸',
+ '𑣙' => '𑢹',
+ '𑣚' => '𑢺',
+ '𑣛' => '𑢻',
+ '𑣜' => '𑢼',
+ '𑣝' => '𑢽',
+ '𑣞' => '𑢾',
+ '𑣟' => '𑢿',
+ '𖹠' => '𖹀',
+ '𖹡' => '𖹁',
+ '𖹢' => '𖹂',
+ '𖹣' => '𖹃',
+ '𖹤' => '𖹄',
+ '𖹥' => '𖹅',
+ '𖹦' => '𖹆',
+ '𖹧' => '𖹇',
+ '𖹨' => '𖹈',
+ '𖹩' => '𖹉',
+ '𖹪' => '𖹊',
+ '𖹫' => '𖹋',
+ '𖹬' => '𖹌',
+ '𖹭' => '𖹍',
+ '𖹮' => '𖹎',
+ '𖹯' => '𖹏',
+ '𖹰' => '𖹐',
+ '𖹱' => '𖹑',
+ '𖹲' => '𖹒',
+ '𖹳' => '𖹓',
+ '𖹴' => '𖹔',
+ '𖹵' => '𖹕',
+ '𖹶' => '𖹖',
+ '𖹷' => '𖹗',
+ '𖹸' => '𖹘',
+ '𖹹' => '𖹙',
+ '𖹺' => '𖹚',
+ '𖹻' => '𖹛',
+ '𖹼' => '𖹜',
+ '𖹽' => '𖹝',
+ '𖹾' => '𖹞',
+ '𖹿' => '𖹟',
+ '𞤢' => '𞤀',
+ '𞤣' => '𞤁',
+ '𞤤' => '𞤂',
+ '𞤥' => '𞤃',
+ '𞤦' => '𞤄',
+ '𞤧' => '𞤅',
+ '𞤨' => '𞤆',
+ '𞤩' => '𞤇',
+ '𞤪' => '𞤈',
+ '𞤫' => '𞤉',
+ '𞤬' => '𞤊',
+ '𞤭' => '𞤋',
+ '𞤮' => '𞤌',
+ '𞤯' => '𞤍',
+ '𞤰' => '𞤎',
+ '𞤱' => '𞤏',
+ '𞤲' => '𞤐',
+ '𞤳' => '𞤑',
+ '𞤴' => '𞤒',
+ '𞤵' => '𞤓',
+ '𞤶' => '𞤔',
+ '𞤷' => '𞤕',
+ '𞤸' => '𞤖',
+ '𞤹' => '𞤗',
+ '𞤺' => '𞤘',
+ '𞤻' => '𞤙',
+ '𞤼' => '𞤚',
+ '𞤽' => '𞤛',
+ '𞤾' => '𞤜',
+ '𞤿' => '𞤝',
+ '𞥀' => '𞤞',
+ '𞥁' => '𞤟',
+ '𞥂' => '𞤠',
+ '𞥃' => '𞤡',
+ 'ß' => 'SS',
+ 'ff' => 'FF',
+ 'fi' => 'FI',
+ 'fl' => 'FL',
+ 'ffi' => 'FFI',
+ 'ffl' => 'FFL',
+ 'ſt' => 'ST',
+ 'st' => 'ST',
+ 'և' => 'ԵՒ',
+ 'ﬓ' => 'ՄՆ',
+ 'ﬔ' => 'ՄԵ',
+ 'ﬕ' => 'ՄԻ',
+ 'ﬖ' => 'ՎՆ',
+ 'ﬗ' => 'ՄԽ',
+ 'ʼn' => 'ʼN',
+ 'ΐ' => 'Ϊ́',
+ 'ΰ' => 'Ϋ́',
+ 'ǰ' => 'J̌',
+ 'ẖ' => 'H̱',
+ 'ẗ' => 'T̈',
+ 'ẘ' => 'W̊',
+ 'ẙ' => 'Y̊',
+ 'ẚ' => 'Aʾ',
+ 'ὐ' => 'Υ̓',
+ 'ὒ' => 'Υ̓̀',
+ 'ὔ' => 'Υ̓́',
+ 'ὖ' => 'Υ̓͂',
+ 'ᾶ' => 'Α͂',
+ 'ῆ' => 'Η͂',
+ 'ῒ' => 'Ϊ̀',
+ 'ΐ' => 'Ϊ́',
+ 'ῖ' => 'Ι͂',
+ 'ῗ' => 'Ϊ͂',
+ 'ῢ' => 'Ϋ̀',
+ 'ΰ' => 'Ϋ́',
+ 'ῤ' => 'Ρ̓',
+ 'ῦ' => 'Υ͂',
+ 'ῧ' => 'Ϋ͂',
+ 'ῶ' => 'Ω͂',
+ 'ᾈ' => 'ἈΙ',
+ 'ᾉ' => 'ἉΙ',
+ 'ᾊ' => 'ἊΙ',
+ 'ᾋ' => 'ἋΙ',
+ 'ᾌ' => 'ἌΙ',
+ 'ᾍ' => 'ἍΙ',
+ 'ᾎ' => 'ἎΙ',
+ 'ᾏ' => 'ἏΙ',
+ 'ᾘ' => 'ἨΙ',
+ 'ᾙ' => 'ἩΙ',
+ 'ᾚ' => 'ἪΙ',
+ 'ᾛ' => 'ἫΙ',
+ 'ᾜ' => 'ἬΙ',
+ 'ᾝ' => 'ἭΙ',
+ 'ᾞ' => 'ἮΙ',
+ 'ᾟ' => 'ἯΙ',
+ 'ᾨ' => 'ὨΙ',
+ 'ᾩ' => 'ὩΙ',
+ 'ᾪ' => 'ὪΙ',
+ 'ᾫ' => 'ὫΙ',
+ 'ᾬ' => 'ὬΙ',
+ 'ᾭ' => 'ὭΙ',
+ 'ᾮ' => 'ὮΙ',
+ 'ᾯ' => 'ὯΙ',
+ 'ᾼ' => 'ΑΙ',
+ 'ῌ' => 'ΗΙ',
+ 'ῼ' => 'ΩΙ',
+ 'ᾲ' => 'ᾺΙ',
+ 'ᾴ' => 'ΆΙ',
+ 'ῂ' => 'ῊΙ',
+ 'ῄ' => 'ΉΙ',
+ 'ῲ' => 'ῺΙ',
+ 'ῴ' => 'ΏΙ',
+ 'ᾷ' => 'Α͂Ι',
+ 'ῇ' => 'Η͂Ι',
+ 'ῷ' => 'Ω͂Ι',
+);
diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php
new file mode 100644
index 000000000..1fedd1f7c
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/bootstrap.php
@@ -0,0 +1,147 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Mbstring as p;
+
+if (\PHP_VERSION_ID >= 80000) {
+ return require __DIR__.'/bootstrap80.php';
+}
+
+if (!function_exists('mb_convert_encoding')) {
+ function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); }
+}
+if (!function_exists('mb_decode_mimeheader')) {
+ function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); }
+}
+if (!function_exists('mb_encode_mimeheader')) {
+ function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); }
+}
+if (!function_exists('mb_decode_numericentity')) {
+ function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); }
+}
+if (!function_exists('mb_encode_numericentity')) {
+ function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); }
+}
+if (!function_exists('mb_convert_case')) {
+ function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); }
+}
+if (!function_exists('mb_internal_encoding')) {
+ function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); }
+}
+if (!function_exists('mb_language')) {
+ function mb_language($language = null) { return p\Mbstring::mb_language($language); }
+}
+if (!function_exists('mb_list_encodings')) {
+ function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
+}
+if (!function_exists('mb_encoding_aliases')) {
+ function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
+}
+if (!function_exists('mb_check_encoding')) {
+ function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); }
+}
+if (!function_exists('mb_detect_encoding')) {
+ function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); }
+}
+if (!function_exists('mb_detect_order')) {
+ function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); }
+}
+if (!function_exists('mb_parse_str')) {
+ function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; }
+}
+if (!function_exists('mb_strlen')) {
+ function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); }
+}
+if (!function_exists('mb_strpos')) {
+ function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); }
+}
+if (!function_exists('mb_strtolower')) {
+ function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); }
+}
+if (!function_exists('mb_strtoupper')) {
+ function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); }
+}
+if (!function_exists('mb_substitute_character')) {
+ function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); }
+}
+if (!function_exists('mb_substr')) {
+ function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); }
+}
+if (!function_exists('mb_stripos')) {
+ function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); }
+}
+if (!function_exists('mb_stristr')) {
+ function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); }
+}
+if (!function_exists('mb_strrchr')) {
+ function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); }
+}
+if (!function_exists('mb_strrichr')) {
+ function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); }
+}
+if (!function_exists('mb_strripos')) {
+ function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); }
+}
+if (!function_exists('mb_strrpos')) {
+ function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); }
+}
+if (!function_exists('mb_strstr')) {
+ function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); }
+}
+if (!function_exists('mb_get_info')) {
+ function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
+}
+if (!function_exists('mb_http_output')) {
+ function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); }
+}
+if (!function_exists('mb_strwidth')) {
+ function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); }
+}
+if (!function_exists('mb_substr_count')) {
+ function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); }
+}
+if (!function_exists('mb_output_handler')) {
+ function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); }
+}
+if (!function_exists('mb_http_input')) {
+ function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); }
+}
+
+if (!function_exists('mb_convert_variables')) {
+ function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); }
+}
+
+if (!function_exists('mb_ord')) {
+ function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); }
+}
+if (!function_exists('mb_chr')) {
+ function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); }
+}
+if (!function_exists('mb_scrub')) {
+ function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); }
+}
+if (!function_exists('mb_str_split')) {
+ function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); }
+}
+
+if (extension_loaded('mbstring')) {
+ return;
+}
+
+if (!defined('MB_CASE_UPPER')) {
+ define('MB_CASE_UPPER', 0);
+}
+if (!defined('MB_CASE_LOWER')) {
+ define('MB_CASE_LOWER', 1);
+}
+if (!defined('MB_CASE_TITLE')) {
+ define('MB_CASE_TITLE', 2);
+}
diff --git a/vendor/symfony/polyfill-mbstring/bootstrap80.php b/vendor/symfony/polyfill-mbstring/bootstrap80.php
new file mode 100644
index 000000000..82f5ac4d0
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/bootstrap80.php
@@ -0,0 +1,143 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Mbstring as p;
+
+if (!function_exists('mb_convert_encoding')) {
+ function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); }
+}
+if (!function_exists('mb_decode_mimeheader')) {
+ function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); }
+}
+if (!function_exists('mb_encode_mimeheader')) {
+ function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); }
+}
+if (!function_exists('mb_decode_numericentity')) {
+ function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); }
+}
+if (!function_exists('mb_encode_numericentity')) {
+ function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); }
+}
+if (!function_exists('mb_convert_case')) {
+ function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); }
+}
+if (!function_exists('mb_internal_encoding')) {
+ function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); }
+}
+if (!function_exists('mb_language')) {
+ function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); }
+}
+if (!function_exists('mb_list_encodings')) {
+ function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); }
+}
+if (!function_exists('mb_encoding_aliases')) {
+ function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); }
+}
+if (!function_exists('mb_check_encoding')) {
+ function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); }
+}
+if (!function_exists('mb_detect_encoding')) {
+ function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); }
+}
+if (!function_exists('mb_detect_order')) {
+ function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); }
+}
+if (!function_exists('mb_parse_str')) {
+ function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; }
+}
+if (!function_exists('mb_strlen')) {
+ function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); }
+}
+if (!function_exists('mb_strpos')) {
+ function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
+}
+if (!function_exists('mb_strtolower')) {
+ function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); }
+}
+if (!function_exists('mb_strtoupper')) {
+ function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); }
+}
+if (!function_exists('mb_substitute_character')) {
+ function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); }
+}
+if (!function_exists('mb_substr')) {
+ function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); }
+}
+if (!function_exists('mb_stripos')) {
+ function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
+}
+if (!function_exists('mb_stristr')) {
+ function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
+}
+if (!function_exists('mb_strrchr')) {
+ function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
+}
+if (!function_exists('mb_strrichr')) {
+ function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
+}
+if (!function_exists('mb_strripos')) {
+ function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
+}
+if (!function_exists('mb_strrpos')) {
+ function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
+}
+if (!function_exists('mb_strstr')) {
+ function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
+}
+if (!function_exists('mb_get_info')) {
+ function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); }
+}
+if (!function_exists('mb_http_output')) {
+ function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); }
+}
+if (!function_exists('mb_strwidth')) {
+ function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); }
+}
+if (!function_exists('mb_substr_count')) {
+ function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); }
+}
+if (!function_exists('mb_output_handler')) {
+ function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); }
+}
+if (!function_exists('mb_http_input')) {
+ function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); }
+}
+
+if (!function_exists('mb_convert_variables')) {
+ function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); }
+}
+
+if (!function_exists('mb_ord')) {
+ function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); }
+}
+if (!function_exists('mb_chr')) {
+ function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); }
+}
+if (!function_exists('mb_scrub')) {
+ function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); }
+}
+if (!function_exists('mb_str_split')) {
+ function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); }
+}
+
+if (extension_loaded('mbstring')) {
+ return;
+}
+
+if (!defined('MB_CASE_UPPER')) {
+ define('MB_CASE_UPPER', 0);
+}
+if (!defined('MB_CASE_LOWER')) {
+ define('MB_CASE_LOWER', 1);
+}
+if (!defined('MB_CASE_TITLE')) {
+ define('MB_CASE_TITLE', 2);
+}
diff --git a/vendor/symfony/polyfill-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json
new file mode 100644
index 000000000..2ed7a7435
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/composer.json
@@ -0,0 +1,38 @@
+{
+ "name": "symfony/polyfill-mbstring",
+ "type": "library",
+ "description": "Symfony polyfill for the Mbstring extension",
+ "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.1"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" },
+ "files": [ "bootstrap.php" ]
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ }
+}
diff --git a/vendor/symfony/polyfill-php72/LICENSE b/vendor/symfony/polyfill-php72/LICENSE
new file mode 100644
index 000000000..4cd8bdd30
--- /dev/null
+++ b/vendor/symfony/polyfill-php72/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/polyfill-php72/Php72.php b/vendor/symfony/polyfill-php72/Php72.php
new file mode 100644
index 000000000..5e20d5bf8
--- /dev/null
+++ b/vendor/symfony/polyfill-php72/Php72.php
@@ -0,0 +1,217 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Php72;
+
+/**
+ * @author Nicolas Grekas
+ * @author Dariusz Rumiński
+ *
+ * @internal
+ */
+final class Php72
+{
+ private static $hashMask;
+
+ public static function utf8_encode($s)
+ {
+ $s .= $s;
+ $len = \strlen($s);
+
+ for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) {
+ switch (true) {
+ case $s[$i] < "\x80": $s[$j] = $s[$i]; break;
+ case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break;
+ default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break;
+ }
+ }
+
+ return substr($s, 0, $j);
+ }
+
+ public static function utf8_decode($s)
+ {
+ $s = (string) $s;
+ $len = \strlen($s);
+
+ for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) {
+ switch ($s[$i] & "\xF0") {
+ case "\xC0":
+ case "\xD0":
+ $c = (\ord($s[$i] & "\x1F") << 6) | \ord($s[++$i] & "\x3F");
+ $s[$j] = $c < 256 ? \chr($c) : '?';
+ break;
+
+ case "\xF0":
+ ++$i;
+ // no break
+
+ case "\xE0":
+ $s[$j] = '?';
+ $i += 2;
+ break;
+
+ default:
+ $s[$j] = $s[$i];
+ }
+ }
+
+ return substr($s, 0, $j);
+ }
+
+ public static function php_os_family()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ return 'Windows';
+ }
+
+ $map = [
+ 'Darwin' => 'Darwin',
+ 'DragonFly' => 'BSD',
+ 'FreeBSD' => 'BSD',
+ 'NetBSD' => 'BSD',
+ 'OpenBSD' => 'BSD',
+ 'Linux' => 'Linux',
+ 'SunOS' => 'Solaris',
+ ];
+
+ return isset($map[\PHP_OS]) ? $map[\PHP_OS] : 'Unknown';
+ }
+
+ public static function spl_object_id($object)
+ {
+ if (null === self::$hashMask) {
+ self::initHashMask();
+ }
+ if (null === $hash = spl_object_hash($object)) {
+ return;
+ }
+
+ // On 32-bit systems, PHP_INT_SIZE is 4,
+ return self::$hashMask ^ hexdec(substr($hash, 16 - (\PHP_INT_SIZE * 2 - 1), (\PHP_INT_SIZE * 2 - 1)));
+ }
+
+ public static function sapi_windows_vt100_support($stream, $enable = null)
+ {
+ if (!\is_resource($stream)) {
+ trigger_error('sapi_windows_vt100_support() expects parameter 1 to be resource, '.\gettype($stream).' given', \E_USER_WARNING);
+
+ return false;
+ }
+
+ $meta = stream_get_meta_data($stream);
+
+ if ('STDIO' !== $meta['stream_type']) {
+ trigger_error('sapi_windows_vt100_support() was not able to analyze the specified stream', \E_USER_WARNING);
+
+ return false;
+ }
+
+ // We cannot actually disable vt100 support if it is set
+ if (false === $enable || !self::stream_isatty($stream)) {
+ return false;
+ }
+
+ // The native function does not apply to stdin
+ $meta = array_map('strtolower', $meta);
+ $stdin = 'php://stdin' === $meta['uri'] || 'php://fd/0' === $meta['uri'];
+
+ return !$stdin
+ && (false !== getenv('ANSICON')
+ || 'ON' === getenv('ConEmuANSI')
+ || 'xterm' === getenv('TERM')
+ || 'Hyper' === getenv('TERM_PROGRAM'));
+ }
+
+ public static function stream_isatty($stream)
+ {
+ if (!\is_resource($stream)) {
+ trigger_error('stream_isatty() expects parameter 1 to be resource, '.\gettype($stream).' given', \E_USER_WARNING);
+
+ return false;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $stat = @fstat($stream);
+ // Check if formatted mode is S_IFCHR
+ return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
+ }
+
+ return \function_exists('posix_isatty') && @posix_isatty($stream);
+ }
+
+ private static function initHashMask()
+ {
+ $obj = (object) [];
+ self::$hashMask = -1;
+
+ // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below
+ $obFuncs = ['ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush'];
+ foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? \DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) {
+ if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && \in_array($frame['function'], $obFuncs)) {
+ $frame['line'] = 0;
+ break;
+ }
+ }
+ if (!empty($frame['line'])) {
+ ob_start();
+ debug_zval_dump($obj);
+ self::$hashMask = (int) substr(ob_get_clean(), 17);
+ }
+
+ self::$hashMask ^= hexdec(substr(spl_object_hash($obj), 16 - (\PHP_INT_SIZE * 2 - 1), (\PHP_INT_SIZE * 2 - 1)));
+ }
+
+ public static function mb_chr($code, $encoding = null)
+ {
+ if (0x80 > $code %= 0x200000) {
+ $s = \chr($code);
+ } elseif (0x800 > $code) {
+ $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
+ } elseif (0x10000 > $code) {
+ $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
+ } else {
+ $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
+ }
+
+ if ('UTF-8' !== $encoding = $encoding ?? mb_internal_encoding()) {
+ $s = mb_convert_encoding($s, $encoding, 'UTF-8');
+ }
+
+ return $s;
+ }
+
+ public static function mb_ord($s, $encoding = null)
+ {
+ if (null === $encoding) {
+ $s = mb_convert_encoding($s, 'UTF-8');
+ } elseif ('UTF-8' !== $encoding) {
+ $s = mb_convert_encoding($s, 'UTF-8', $encoding);
+ }
+
+ if (1 === \strlen($s)) {
+ return \ord($s);
+ }
+
+ $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
+ if (0xF0 <= $code) {
+ return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
+ }
+ if (0xE0 <= $code) {
+ return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
+ }
+ if (0xC0 <= $code) {
+ return (($code - 0xC0) << 6) + $s[2] - 0x80;
+ }
+
+ return $code;
+ }
+}
diff --git a/vendor/symfony/polyfill-php72/README.md b/vendor/symfony/polyfill-php72/README.md
new file mode 100644
index 000000000..59dec8a23
--- /dev/null
+++ b/vendor/symfony/polyfill-php72/README.md
@@ -0,0 +1,28 @@
+Symfony Polyfill / Php72
+========================
+
+This component provides functions added to PHP 7.2 core:
+
+- [`spl_object_id`](https://php.net/spl_object_id)
+- [`stream_isatty`](https://php.net/stream_isatty)
+
+On Windows only:
+
+- [`sapi_windows_vt100_support`](https://php.net/sapi_windows_vt100_support)
+
+Moved to core since 7.2 (was in the optional XML extension earlier):
+
+- [`utf8_encode`](https://php.net/utf8_encode)
+- [`utf8_decode`](https://php.net/utf8_decode)
+
+Also, it provides constants added to PHP 7.2:
+- [`PHP_FLOAT_*`](https://php.net/reserved.constants#constant.php-float-dig)
+- [`PHP_OS_FAMILY`](https://php.net/reserved.constants#constant.php-os-family)
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/vendor/symfony/polyfill-php72/bootstrap.php b/vendor/symfony/polyfill-php72/bootstrap.php
new file mode 100644
index 000000000..b5c92d4c7
--- /dev/null
+++ b/vendor/symfony/polyfill-php72/bootstrap.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Php72 as p;
+
+if (\PHP_VERSION_ID >= 70200) {
+ return;
+}
+
+if (!defined('PHP_FLOAT_DIG')) {
+ define('PHP_FLOAT_DIG', 15);
+}
+if (!defined('PHP_FLOAT_EPSILON')) {
+ define('PHP_FLOAT_EPSILON', 2.2204460492503E-16);
+}
+if (!defined('PHP_FLOAT_MIN')) {
+ define('PHP_FLOAT_MIN', 2.2250738585072E-308);
+}
+if (!defined('PHP_FLOAT_MAX')) {
+ define('PHP_FLOAT_MAX', 1.7976931348623157E+308);
+}
+if (!defined('PHP_OS_FAMILY')) {
+ define('PHP_OS_FAMILY', p\Php72::php_os_family());
+}
+
+if ('\\' === \DIRECTORY_SEPARATOR && !function_exists('sapi_windows_vt100_support')) {
+ function sapi_windows_vt100_support($stream, $enable = null) { return p\Php72::sapi_windows_vt100_support($stream, $enable); }
+}
+if (!function_exists('stream_isatty')) {
+ function stream_isatty($stream) { return p\Php72::stream_isatty($stream); }
+}
+if (!function_exists('utf8_encode')) {
+ function utf8_encode($string) { return p\Php72::utf8_encode($string); }
+}
+if (!function_exists('utf8_decode')) {
+ function utf8_decode($string) { return p\Php72::utf8_decode($string); }
+}
+if (!function_exists('spl_object_id')) {
+ function spl_object_id($object) { return p\Php72::spl_object_id($object); }
+}
+if (!function_exists('mb_ord')) {
+ function mb_ord($string, $encoding = null) { return p\Php72::mb_ord($string, $encoding); }
+}
+if (!function_exists('mb_chr')) {
+ function mb_chr($codepoint, $encoding = null) { return p\Php72::mb_chr($codepoint, $encoding); }
+}
+if (!function_exists('mb_scrub')) {
+ function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); }
+}
diff --git a/vendor/symfony/polyfill-php72/composer.json b/vendor/symfony/polyfill-php72/composer.json
new file mode 100644
index 000000000..c96c84477
--- /dev/null
+++ b/vendor/symfony/polyfill-php72/composer.json
@@ -0,0 +1,35 @@
+{
+ "name": "symfony/polyfill-php72",
+ "type": "library",
+ "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+ "keywords": ["polyfill", "shim", "compatibility", "portable"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.1"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Polyfill\\Php72\\": "" },
+ "files": [ "bootstrap.php" ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ }
+}
diff --git a/vendor/symfony/polyfill-php80/LICENSE b/vendor/symfony/polyfill-php80/LICENSE
new file mode 100644
index 000000000..5593b1d84
--- /dev/null
+++ b/vendor/symfony/polyfill-php80/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2020 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/polyfill-php80/Php80.php b/vendor/symfony/polyfill-php80/Php80.php
new file mode 100644
index 000000000..5fef51184
--- /dev/null
+++ b/vendor/symfony/polyfill-php80/Php80.php
@@ -0,0 +1,105 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Php80;
+
+/**
+ * @author Ion Bazan
+ * @author Nico Oelgart
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+final class Php80
+{
+ public static function fdiv(float $dividend, float $divisor): float
+ {
+ return @($dividend / $divisor);
+ }
+
+ public static function get_debug_type($value): string
+ {
+ switch (true) {
+ case null === $value: return 'null';
+ case \is_bool($value): return 'bool';
+ case \is_string($value): return 'string';
+ case \is_array($value): return 'array';
+ case \is_int($value): return 'int';
+ case \is_float($value): return 'float';
+ case \is_object($value): break;
+ case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class';
+ default:
+ if (null === $type = @get_resource_type($value)) {
+ return 'unknown';
+ }
+
+ if ('Unknown' === $type) {
+ $type = 'closed';
+ }
+
+ return "resource ($type)";
+ }
+
+ $class = \get_class($value);
+
+ if (false === strpos($class, '@')) {
+ return $class;
+ }
+
+ return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous';
+ }
+
+ public static function get_resource_id($res): int
+ {
+ if (!\is_resource($res) && null === @get_resource_type($res)) {
+ throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res)));
+ }
+
+ return (int) $res;
+ }
+
+ public static function preg_last_error_msg(): string
+ {
+ switch (preg_last_error()) {
+ case \PREG_INTERNAL_ERROR:
+ return 'Internal error';
+ case \PREG_BAD_UTF8_ERROR:
+ return 'Malformed UTF-8 characters, possibly incorrectly encoded';
+ case \PREG_BAD_UTF8_OFFSET_ERROR:
+ return 'The offset did not correspond to the beginning of a valid UTF-8 code point';
+ case \PREG_BACKTRACK_LIMIT_ERROR:
+ return 'Backtrack limit exhausted';
+ case \PREG_RECURSION_LIMIT_ERROR:
+ return 'Recursion limit exhausted';
+ case \PREG_JIT_STACKLIMIT_ERROR:
+ return 'JIT stack limit exhausted';
+ case \PREG_NO_ERROR:
+ return 'No error';
+ default:
+ return 'Unknown error';
+ }
+ }
+
+ public static function str_contains(string $haystack, string $needle): bool
+ {
+ return '' === $needle || false !== strpos($haystack, $needle);
+ }
+
+ public static function str_starts_with(string $haystack, string $needle): bool
+ {
+ return 0 === strncmp($haystack, $needle, \strlen($needle));
+ }
+
+ public static function str_ends_with(string $haystack, string $needle): bool
+ {
+ return '' === $needle || ('' !== $haystack && 0 === substr_compare($haystack, $needle, -\strlen($needle)));
+ }
+}
diff --git a/vendor/symfony/polyfill-php80/README.md b/vendor/symfony/polyfill-php80/README.md
new file mode 100644
index 000000000..eaa3050ab
--- /dev/null
+++ b/vendor/symfony/polyfill-php80/README.md
@@ -0,0 +1,24 @@
+Symfony Polyfill / Php80
+========================
+
+This component provides features added to PHP 8.0 core:
+
+- `Stringable` interface
+- [`fdiv`](https://php.net/fdiv)
+- `ValueError` class
+- `UnhandledMatchError` class
+- `FILTER_VALIDATE_BOOL` constant
+- [`get_debug_type`](https://php.net/get_debug_type)
+- [`preg_last_error_msg`](https://php.net/preg_last_error_msg)
+- [`str_contains`](https://php.net/str_contains)
+- [`str_starts_with`](https://php.net/str_starts_with)
+- [`str_ends_with`](https://php.net/str_ends_with)
+- [`get_resource_id`](https://php.net/get_resource_id)
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php
new file mode 100644
index 000000000..7ea6d2772
--- /dev/null
+++ b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php
@@ -0,0 +1,22 @@
+flags = $flags;
+ }
+}
diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php
new file mode 100644
index 000000000..77e037cb5
--- /dev/null
+++ b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php
@@ -0,0 +1,11 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Php80 as p;
+
+if (\PHP_VERSION_ID >= 80000) {
+ return;
+}
+
+if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) {
+ define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN);
+}
+
+if (!function_exists('fdiv')) {
+ function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); }
+}
+if (!function_exists('preg_last_error_msg')) {
+ function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); }
+}
+if (!function_exists('str_contains')) {
+ function str_contains(string $haystack, string $needle): bool { return p\Php80::str_contains($haystack, $needle); }
+}
+if (!function_exists('str_starts_with')) {
+ function str_starts_with(string $haystack, string $needle): bool { return p\Php80::str_starts_with($haystack, $needle); }
+}
+if (!function_exists('str_ends_with')) {
+ function str_ends_with(string $haystack, string $needle): bool { return p\Php80::str_ends_with($haystack, $needle); }
+}
+if (!function_exists('get_debug_type')) {
+ function get_debug_type($value): string { return p\Php80::get_debug_type($value); }
+}
+if (!function_exists('get_resource_id')) {
+ function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); }
+}
diff --git a/vendor/symfony/polyfill-php80/composer.json b/vendor/symfony/polyfill-php80/composer.json
new file mode 100644
index 000000000..5fe679db3
--- /dev/null
+++ b/vendor/symfony/polyfill-php80/composer.json
@@ -0,0 +1,40 @@
+{
+ "name": "symfony/polyfill-php80",
+ "type": "library",
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "keywords": ["polyfill", "shim", "compatibility", "portable"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.1"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Polyfill\\Php80\\": "" },
+ "files": [ "bootstrap.php" ],
+ "classmap": [ "Resources/stubs" ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ }
+}
diff --git a/vendor/symfony/var-dumper/CHANGELOG.md b/vendor/symfony/var-dumper/CHANGELOG.md
new file mode 100644
index 000000000..94b1c17d1
--- /dev/null
+++ b/vendor/symfony/var-dumper/CHANGELOG.md
@@ -0,0 +1,53 @@
+CHANGELOG
+=========
+
+4.4.0
+-----
+
+ * added `VarDumperTestTrait::setUpVarDumper()` and `VarDumperTestTrait::tearDownVarDumper()`
+ to configure casters & flags to use in tests
+ * added `ImagineCaster` and infrastructure to dump images
+ * added the stamps of a message after it is dispatched in `TraceableMessageBus` and `MessengerDataCollector` collected data
+ * added `UuidCaster`
+ * made all casters final
+ * added support for the `NO_COLOR` env var (https://no-color.org/)
+
+4.3.0
+-----
+
+ * added `DsCaster` to support dumping the contents of data structures from the Ds extension
+
+4.2.0
+-----
+
+ * support selecting the format to use by setting the environment variable `VAR_DUMPER_FORMAT` to `html` or `cli`
+
+4.1.0
+-----
+
+ * added a `ServerDumper` to send serialized Data clones to a server
+ * added a `ServerDumpCommand` and `DumpServer` to run a server collecting
+ and displaying dumps on a single place with multiple formats support
+ * added `CliDescriptor` and `HtmlDescriptor` descriptors for `server:dump` CLI and HTML formats support
+
+4.0.0
+-----
+
+ * support for passing `\ReflectionClass` instances to the `Caster::castObject()`
+ method has been dropped, pass class names as strings instead
+ * the `Data::getRawData()` method has been removed
+ * the `VarDumperTestTrait::assertDumpEquals()` method expects a 3rd `$filter = 0`
+ argument and moves `$message = ''` argument at 4th position.
+ * the `VarDumperTestTrait::assertDumpMatchesFormat()` method expects a 3rd `$filter = 0`
+ argument and moves `$message = ''` argument at 4th position.
+
+3.4.0
+-----
+
+ * added `AbstractCloner::setMinDepth()` function to ensure minimum tree depth
+ * deprecated `MongoCaster`
+
+2.7.0
+-----
+
+ * deprecated `Cloner\Data::getLimitedClone()`. Use `withMaxDepth`, `withMaxItemsPerDepth` or `withRefHandles` instead.
diff --git a/vendor/symfony/var-dumper/Caster/AmqpCaster.php b/vendor/symfony/var-dumper/Caster/AmqpCaster.php
new file mode 100644
index 000000000..60045ff7b
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/AmqpCaster.php
@@ -0,0 +1,212 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts Amqp related classes to array representation.
+ *
+ * @author Grégoire Pineau
+ *
+ * @final since Symfony 4.4
+ */
+class AmqpCaster
+{
+ private const FLAGS = [
+ \AMQP_DURABLE => 'AMQP_DURABLE',
+ \AMQP_PASSIVE => 'AMQP_PASSIVE',
+ \AMQP_EXCLUSIVE => 'AMQP_EXCLUSIVE',
+ \AMQP_AUTODELETE => 'AMQP_AUTODELETE',
+ \AMQP_INTERNAL => 'AMQP_INTERNAL',
+ \AMQP_NOLOCAL => 'AMQP_NOLOCAL',
+ \AMQP_AUTOACK => 'AMQP_AUTOACK',
+ \AMQP_IFEMPTY => 'AMQP_IFEMPTY',
+ \AMQP_IFUNUSED => 'AMQP_IFUNUSED',
+ \AMQP_MANDATORY => 'AMQP_MANDATORY',
+ \AMQP_IMMEDIATE => 'AMQP_IMMEDIATE',
+ \AMQP_MULTIPLE => 'AMQP_MULTIPLE',
+ \AMQP_NOWAIT => 'AMQP_NOWAIT',
+ \AMQP_REQUEUE => 'AMQP_REQUEUE',
+ ];
+
+ private const EXCHANGE_TYPES = [
+ \AMQP_EX_TYPE_DIRECT => 'AMQP_EX_TYPE_DIRECT',
+ \AMQP_EX_TYPE_FANOUT => 'AMQP_EX_TYPE_FANOUT',
+ \AMQP_EX_TYPE_TOPIC => 'AMQP_EX_TYPE_TOPIC',
+ \AMQP_EX_TYPE_HEADERS => 'AMQP_EX_TYPE_HEADERS',
+ ];
+
+ public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ $a += [
+ $prefix.'is_connected' => $c->isConnected(),
+ ];
+
+ // Recent version of the extension already expose private properties
+ if (isset($a["\x00AMQPConnection\x00login"])) {
+ return $a;
+ }
+
+ // BC layer in the amqp lib
+ if (method_exists($c, 'getReadTimeout')) {
+ $timeout = $c->getReadTimeout();
+ } else {
+ $timeout = $c->getTimeout();
+ }
+
+ $a += [
+ $prefix.'is_connected' => $c->isConnected(),
+ $prefix.'login' => $c->getLogin(),
+ $prefix.'password' => $c->getPassword(),
+ $prefix.'host' => $c->getHost(),
+ $prefix.'vhost' => $c->getVhost(),
+ $prefix.'port' => $c->getPort(),
+ $prefix.'read_timeout' => $timeout,
+ ];
+
+ return $a;
+ }
+
+ public static function castChannel(\AMQPChannel $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ $a += [
+ $prefix.'is_connected' => $c->isConnected(),
+ $prefix.'channel_id' => $c->getChannelId(),
+ ];
+
+ // Recent version of the extension already expose private properties
+ if (isset($a["\x00AMQPChannel\x00connection"])) {
+ return $a;
+ }
+
+ $a += [
+ $prefix.'connection' => $c->getConnection(),
+ $prefix.'prefetch_size' => $c->getPrefetchSize(),
+ $prefix.'prefetch_count' => $c->getPrefetchCount(),
+ ];
+
+ return $a;
+ }
+
+ public static function castQueue(\AMQPQueue $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ $a += [
+ $prefix.'flags' => self::extractFlags($c->getFlags()),
+ ];
+
+ // Recent version of the extension already expose private properties
+ if (isset($a["\x00AMQPQueue\x00name"])) {
+ return $a;
+ }
+
+ $a += [
+ $prefix.'connection' => $c->getConnection(),
+ $prefix.'channel' => $c->getChannel(),
+ $prefix.'name' => $c->getName(),
+ $prefix.'arguments' => $c->getArguments(),
+ ];
+
+ return $a;
+ }
+
+ public static function castExchange(\AMQPExchange $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ $a += [
+ $prefix.'flags' => self::extractFlags($c->getFlags()),
+ ];
+
+ $type = isset(self::EXCHANGE_TYPES[$c->getType()]) ? new ConstStub(self::EXCHANGE_TYPES[$c->getType()], $c->getType()) : $c->getType();
+
+ // Recent version of the extension already expose private properties
+ if (isset($a["\x00AMQPExchange\x00name"])) {
+ $a["\x00AMQPExchange\x00type"] = $type;
+
+ return $a;
+ }
+
+ $a += [
+ $prefix.'connection' => $c->getConnection(),
+ $prefix.'channel' => $c->getChannel(),
+ $prefix.'name' => $c->getName(),
+ $prefix.'type' => $type,
+ $prefix.'arguments' => $c->getArguments(),
+ ];
+
+ return $a;
+ }
+
+ public static function castEnvelope(\AMQPEnvelope $c, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ $deliveryMode = new ConstStub($c->getDeliveryMode().(2 === $c->getDeliveryMode() ? ' (persistent)' : ' (non-persistent)'), $c->getDeliveryMode());
+
+ // Recent version of the extension already expose private properties
+ if (isset($a["\x00AMQPEnvelope\x00body"])) {
+ $a["\0AMQPEnvelope\0delivery_mode"] = $deliveryMode;
+
+ return $a;
+ }
+
+ if (!($filter & Caster::EXCLUDE_VERBOSE)) {
+ $a += [$prefix.'body' => $c->getBody()];
+ }
+
+ $a += [
+ $prefix.'delivery_tag' => $c->getDeliveryTag(),
+ $prefix.'is_redelivery' => $c->isRedelivery(),
+ $prefix.'exchange_name' => $c->getExchangeName(),
+ $prefix.'routing_key' => $c->getRoutingKey(),
+ $prefix.'content_type' => $c->getContentType(),
+ $prefix.'content_encoding' => $c->getContentEncoding(),
+ $prefix.'headers' => $c->getHeaders(),
+ $prefix.'delivery_mode' => $deliveryMode,
+ $prefix.'priority' => $c->getPriority(),
+ $prefix.'correlation_id' => $c->getCorrelationId(),
+ $prefix.'reply_to' => $c->getReplyTo(),
+ $prefix.'expiration' => $c->getExpiration(),
+ $prefix.'message_id' => $c->getMessageId(),
+ $prefix.'timestamp' => $c->getTimeStamp(),
+ $prefix.'type' => $c->getType(),
+ $prefix.'user_id' => $c->getUserId(),
+ $prefix.'app_id' => $c->getAppId(),
+ ];
+
+ return $a;
+ }
+
+ private static function extractFlags(int $flags): ConstStub
+ {
+ $flagsArray = [];
+
+ foreach (self::FLAGS as $value => $name) {
+ if ($flags & $value) {
+ $flagsArray[] = $name;
+ }
+ }
+
+ if (!$flagsArray) {
+ $flagsArray = ['AMQP_NOPARAM'];
+ }
+
+ return new ConstStub(implode('|', $flagsArray), $flags);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ArgsStub.php b/vendor/symfony/var-dumper/Caster/ArgsStub.php
new file mode 100644
index 000000000..f8b485bd4
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ArgsStub.php
@@ -0,0 +1,80 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Represents a list of function arguments.
+ *
+ * @author Nicolas Grekas
+ */
+class ArgsStub extends EnumStub
+{
+ private static $parameters = [];
+
+ public function __construct(array $args, string $function, ?string $class)
+ {
+ [$variadic, $params] = self::getParameters($function, $class);
+
+ $values = [];
+ foreach ($args as $k => $v) {
+ $values[$k] = !is_scalar($v) && !$v instanceof Stub ? new CutStub($v) : $v;
+ }
+ if (null === $params) {
+ parent::__construct($values, false);
+
+ return;
+ }
+ if (\count($values) < \count($params)) {
+ $params = \array_slice($params, 0, \count($values));
+ } elseif (\count($values) > \count($params)) {
+ $values[] = new EnumStub(array_splice($values, \count($params)), false);
+ $params[] = $variadic;
+ }
+ if (['...'] === $params) {
+ $this->dumpKeys = false;
+ $this->value = $values[0]->value;
+ } else {
+ $this->value = array_combine($params, $values);
+ }
+ }
+
+ private static function getParameters(string $function, ?string $class): array
+ {
+ if (isset(self::$parameters[$k = $class.'::'.$function])) {
+ return self::$parameters[$k];
+ }
+
+ try {
+ $r = null !== $class ? new \ReflectionMethod($class, $function) : new \ReflectionFunction($function);
+ } catch (\ReflectionException $e) {
+ return [null, null];
+ }
+
+ $variadic = '...';
+ $params = [];
+ foreach ($r->getParameters() as $v) {
+ $k = '$'.$v->name;
+ if ($v->isPassedByReference()) {
+ $k = '&'.$k;
+ }
+ if ($v->isVariadic()) {
+ $variadic .= $k;
+ } else {
+ $params[] = $k;
+ }
+ }
+
+ return self::$parameters[$k] = [$variadic, $params];
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/Caster.php b/vendor/symfony/var-dumper/Caster/Caster.php
new file mode 100644
index 000000000..d35f3230b
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/Caster.php
@@ -0,0 +1,175 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Helper for filtering out properties in casters.
+ *
+ * @author Nicolas Grekas
+ *
+ * @final
+ */
+class Caster
+{
+ public const EXCLUDE_VERBOSE = 1;
+ public const EXCLUDE_VIRTUAL = 2;
+ public const EXCLUDE_DYNAMIC = 4;
+ public const EXCLUDE_PUBLIC = 8;
+ public const EXCLUDE_PROTECTED = 16;
+ public const EXCLUDE_PRIVATE = 32;
+ public const EXCLUDE_NULL = 64;
+ public const EXCLUDE_EMPTY = 128;
+ public const EXCLUDE_NOT_IMPORTANT = 256;
+ public const EXCLUDE_STRICT = 512;
+
+ public const PREFIX_VIRTUAL = "\0~\0";
+ public const PREFIX_DYNAMIC = "\0+\0";
+ public const PREFIX_PROTECTED = "\0*\0";
+
+ /**
+ * Casts objects to arrays and adds the dynamic property prefix.
+ *
+ * @param object $obj The object to cast
+ * @param bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not
+ *
+ * @return array The array-cast of the object, with prefixed dynamic properties
+ */
+ public static function castObject($obj, string $class, bool $hasDebugInfo = false, string $debugClass = null): array
+ {
+ if ($hasDebugInfo) {
+ try {
+ $debugInfo = $obj->__debugInfo();
+ } catch (\Exception $e) {
+ // ignore failing __debugInfo()
+ $hasDebugInfo = false;
+ }
+ }
+
+ $a = $obj instanceof \Closure ? [] : (array) $obj;
+
+ if ($obj instanceof \__PHP_Incomplete_Class) {
+ return $a;
+ }
+
+ if ($a) {
+ static $publicProperties = [];
+ $debugClass = $debugClass ?? get_debug_type($obj);
+
+ $i = 0;
+ $prefixedKeys = [];
+ foreach ($a as $k => $v) {
+ if (isset($k[0]) ? "\0" !== $k[0] : \PHP_VERSION_ID >= 70200) {
+ if (!isset($publicProperties[$class])) {
+ foreach ((new \ReflectionClass($class))->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
+ $publicProperties[$class][$prop->name] = true;
+ }
+ }
+ if (!isset($publicProperties[$class][$k])) {
+ $prefixedKeys[$i] = self::PREFIX_DYNAMIC.$k;
+ }
+ } elseif ($debugClass !== $class && 1 === strpos($k, $class)) {
+ $prefixedKeys[$i] = "\0".$debugClass.strrchr($k, "\0");
+ }
+ ++$i;
+ }
+ if ($prefixedKeys) {
+ $keys = array_keys($a);
+ foreach ($prefixedKeys as $i => $k) {
+ $keys[$i] = $k;
+ }
+ $a = array_combine($keys, $a);
+ }
+ }
+
+ if ($hasDebugInfo && \is_array($debugInfo)) {
+ foreach ($debugInfo as $k => $v) {
+ if (!isset($k[0]) || "\0" !== $k[0]) {
+ if (\array_key_exists(self::PREFIX_DYNAMIC.$k, $a)) {
+ continue;
+ }
+ $k = self::PREFIX_VIRTUAL.$k;
+ }
+
+ unset($a[$k]);
+ $a[$k] = $v;
+ }
+ }
+
+ return $a;
+ }
+
+ /**
+ * Filters out the specified properties.
+ *
+ * By default, a single match in the $filter bit field filters properties out, following an "or" logic.
+ * When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed.
+ *
+ * @param array $a The array containing the properties to filter
+ * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out
+ * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set
+ * @param int &$count Set to the number of removed properties
+ *
+ * @return array The filtered array
+ */
+ public static function filter(array $a, int $filter, array $listedProperties = [], ?int &$count = 0): array
+ {
+ $count = 0;
+
+ foreach ($a as $k => $v) {
+ $type = self::EXCLUDE_STRICT & $filter;
+
+ if (null === $v) {
+ $type |= self::EXCLUDE_NULL & $filter;
+ $type |= self::EXCLUDE_EMPTY & $filter;
+ } elseif (false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || [] === $v) {
+ $type |= self::EXCLUDE_EMPTY & $filter;
+ }
+ if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !\in_array($k, $listedProperties, true)) {
+ $type |= self::EXCLUDE_NOT_IMPORTANT;
+ }
+ if ((self::EXCLUDE_VERBOSE & $filter) && \in_array($k, $listedProperties, true)) {
+ $type |= self::EXCLUDE_VERBOSE;
+ }
+
+ if (!isset($k[1]) || "\0" !== $k[0]) {
+ $type |= self::EXCLUDE_PUBLIC & $filter;
+ } elseif ('~' === $k[1]) {
+ $type |= self::EXCLUDE_VIRTUAL & $filter;
+ } elseif ('+' === $k[1]) {
+ $type |= self::EXCLUDE_DYNAMIC & $filter;
+ } elseif ('*' === $k[1]) {
+ $type |= self::EXCLUDE_PROTECTED & $filter;
+ } else {
+ $type |= self::EXCLUDE_PRIVATE & $filter;
+ }
+
+ if ((self::EXCLUDE_STRICT & $filter) ? $type === $filter : $type) {
+ unset($a[$k]);
+ ++$count;
+ }
+ }
+
+ return $a;
+ }
+
+ public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array $a, Stub $stub, bool $isNested): array
+ {
+ if (isset($a['__PHP_Incomplete_Class_Name'])) {
+ $stub->class .= '('.$a['__PHP_Incomplete_Class_Name'].')';
+ unset($a['__PHP_Incomplete_Class_Name']);
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ClassStub.php b/vendor/symfony/var-dumper/Caster/ClassStub.php
new file mode 100644
index 000000000..612a7ca2d
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ClassStub.php
@@ -0,0 +1,106 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Represents a PHP class identifier.
+ *
+ * @author Nicolas Grekas
+ */
+class ClassStub extends ConstStub
+{
+ /**
+ * @param string $identifier A PHP identifier, e.g. a class, method, interface, etc. name
+ * @param callable $callable The callable targeted by the identifier when it is ambiguous or not a real PHP identifier
+ */
+ public function __construct(string $identifier, $callable = null)
+ {
+ $this->value = $identifier;
+
+ try {
+ if (null !== $callable) {
+ if ($callable instanceof \Closure) {
+ $r = new \ReflectionFunction($callable);
+ } elseif (\is_object($callable)) {
+ $r = [$callable, '__invoke'];
+ } elseif (\is_array($callable)) {
+ $r = $callable;
+ } elseif (false !== $i = strpos($callable, '::')) {
+ $r = [substr($callable, 0, $i), substr($callable, 2 + $i)];
+ } else {
+ $r = new \ReflectionFunction($callable);
+ }
+ } elseif (0 < $i = strpos($identifier, '::') ?: strpos($identifier, '->')) {
+ $r = [substr($identifier, 0, $i), substr($identifier, 2 + $i)];
+ } else {
+ $r = new \ReflectionClass($identifier);
+ }
+
+ if (\is_array($r)) {
+ try {
+ $r = new \ReflectionMethod($r[0], $r[1]);
+ } catch (\ReflectionException $e) {
+ $r = new \ReflectionClass($r[0]);
+ }
+ }
+
+ if (false !== strpos($identifier, "@anonymous\0")) {
+ $this->value = $identifier = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
+ return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
+ }, $identifier);
+ }
+
+ if (null !== $callable && $r instanceof \ReflectionFunctionAbstract) {
+ $s = ReflectionCaster::castFunctionAbstract($r, [], new Stub(), true, Caster::EXCLUDE_VERBOSE);
+ $s = ReflectionCaster::getSignature($s);
+
+ if ('()' === substr($identifier, -2)) {
+ $this->value = substr_replace($identifier, $s, -2);
+ } else {
+ $this->value .= $s;
+ }
+ }
+ } catch (\ReflectionException $e) {
+ return;
+ } finally {
+ if (0 < $i = strrpos($this->value, '\\')) {
+ $this->attr['ellipsis'] = \strlen($this->value) - $i;
+ $this->attr['ellipsis-type'] = 'class';
+ $this->attr['ellipsis-tail'] = 1;
+ }
+ }
+
+ if ($f = $r->getFileName()) {
+ $this->attr['file'] = $f;
+ $this->attr['line'] = $r->getStartLine();
+ }
+ }
+
+ public static function wrapCallable($callable)
+ {
+ if (\is_object($callable) || !\is_callable($callable)) {
+ return $callable;
+ }
+
+ if (!\is_array($callable)) {
+ $callable = new static($callable, $callable);
+ } elseif (\is_string($callable[0])) {
+ $callable[0] = new static($callable[0], $callable);
+ } else {
+ $callable[1] = new static($callable[1], $callable);
+ }
+
+ return $callable;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ConstStub.php b/vendor/symfony/var-dumper/Caster/ConstStub.php
new file mode 100644
index 000000000..8b0179745
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ConstStub.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Represents a PHP constant and its value.
+ *
+ * @author Nicolas Grekas
+ */
+class ConstStub extends Stub
+{
+ public function __construct(string $name, $value = null)
+ {
+ $this->class = $name;
+ $this->value = 1 < \func_num_args() ? $value : $name;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->value;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/CutArrayStub.php b/vendor/symfony/var-dumper/Caster/CutArrayStub.php
new file mode 100644
index 000000000..0e4fb363d
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/CutArrayStub.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+/**
+ * Represents a cut array.
+ *
+ * @author Nicolas Grekas
+ */
+class CutArrayStub extends CutStub
+{
+ public $preservedSubset;
+
+ public function __construct(array $value, array $preservedKeys)
+ {
+ parent::__construct($value);
+
+ $this->preservedSubset = array_intersect_key($value, array_flip($preservedKeys));
+ $this->cut -= \count($this->preservedSubset);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/CutStub.php b/vendor/symfony/var-dumper/Caster/CutStub.php
new file mode 100644
index 000000000..464c6dbd1
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/CutStub.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Represents the main properties of a PHP variable, pre-casted by a caster.
+ *
+ * @author Nicolas Grekas
+ */
+class CutStub extends Stub
+{
+ public function __construct($value)
+ {
+ $this->value = $value;
+
+ switch (\gettype($value)) {
+ case 'object':
+ $this->type = self::TYPE_OBJECT;
+ $this->class = \get_class($value);
+
+ if ($value instanceof \Closure) {
+ ReflectionCaster::castClosure($value, [], $this, true, Caster::EXCLUDE_VERBOSE);
+ }
+
+ $this->cut = -1;
+ break;
+
+ case 'array':
+ $this->type = self::TYPE_ARRAY;
+ $this->class = self::ARRAY_ASSOC;
+ $this->cut = $this->value = \count($value);
+ break;
+
+ case 'resource':
+ case 'unknown type':
+ case 'resource (closed)':
+ $this->type = self::TYPE_RESOURCE;
+ $this->handle = (int) $value;
+ if ('Unknown' === $this->class = @get_resource_type($value)) {
+ $this->class = 'Closed';
+ }
+ $this->cut = -1;
+ break;
+
+ case 'string':
+ $this->type = self::TYPE_STRING;
+ $this->class = preg_match('//u', $value) ? self::STRING_UTF8 : self::STRING_BINARY;
+ $this->cut = self::STRING_BINARY === $this->class ? \strlen($value) : mb_strlen($value, 'UTF-8');
+ $this->value = '';
+ break;
+ }
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/DOMCaster.php b/vendor/symfony/var-dumper/Caster/DOMCaster.php
new file mode 100644
index 000000000..5f2b9cd11
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/DOMCaster.php
@@ -0,0 +1,304 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts DOM related classes to array representation.
+ *
+ * @author Nicolas Grekas
+ *
+ * @final since Symfony 4.4
+ */
+class DOMCaster
+{
+ private const ERROR_CODES = [
+ \DOM_PHP_ERR => 'DOM_PHP_ERR',
+ \DOM_INDEX_SIZE_ERR => 'DOM_INDEX_SIZE_ERR',
+ \DOMSTRING_SIZE_ERR => 'DOMSTRING_SIZE_ERR',
+ \DOM_HIERARCHY_REQUEST_ERR => 'DOM_HIERARCHY_REQUEST_ERR',
+ \DOM_WRONG_DOCUMENT_ERR => 'DOM_WRONG_DOCUMENT_ERR',
+ \DOM_INVALID_CHARACTER_ERR => 'DOM_INVALID_CHARACTER_ERR',
+ \DOM_NO_DATA_ALLOWED_ERR => 'DOM_NO_DATA_ALLOWED_ERR',
+ \DOM_NO_MODIFICATION_ALLOWED_ERR => 'DOM_NO_MODIFICATION_ALLOWED_ERR',
+ \DOM_NOT_FOUND_ERR => 'DOM_NOT_FOUND_ERR',
+ \DOM_NOT_SUPPORTED_ERR => 'DOM_NOT_SUPPORTED_ERR',
+ \DOM_INUSE_ATTRIBUTE_ERR => 'DOM_INUSE_ATTRIBUTE_ERR',
+ \DOM_INVALID_STATE_ERR => 'DOM_INVALID_STATE_ERR',
+ \DOM_SYNTAX_ERR => 'DOM_SYNTAX_ERR',
+ \DOM_INVALID_MODIFICATION_ERR => 'DOM_INVALID_MODIFICATION_ERR',
+ \DOM_NAMESPACE_ERR => 'DOM_NAMESPACE_ERR',
+ \DOM_INVALID_ACCESS_ERR => 'DOM_INVALID_ACCESS_ERR',
+ \DOM_VALIDATION_ERR => 'DOM_VALIDATION_ERR',
+ ];
+
+ private const NODE_TYPES = [
+ \XML_ELEMENT_NODE => 'XML_ELEMENT_NODE',
+ \XML_ATTRIBUTE_NODE => 'XML_ATTRIBUTE_NODE',
+ \XML_TEXT_NODE => 'XML_TEXT_NODE',
+ \XML_CDATA_SECTION_NODE => 'XML_CDATA_SECTION_NODE',
+ \XML_ENTITY_REF_NODE => 'XML_ENTITY_REF_NODE',
+ \XML_ENTITY_NODE => 'XML_ENTITY_NODE',
+ \XML_PI_NODE => 'XML_PI_NODE',
+ \XML_COMMENT_NODE => 'XML_COMMENT_NODE',
+ \XML_DOCUMENT_NODE => 'XML_DOCUMENT_NODE',
+ \XML_DOCUMENT_TYPE_NODE => 'XML_DOCUMENT_TYPE_NODE',
+ \XML_DOCUMENT_FRAG_NODE => 'XML_DOCUMENT_FRAG_NODE',
+ \XML_NOTATION_NODE => 'XML_NOTATION_NODE',
+ \XML_HTML_DOCUMENT_NODE => 'XML_HTML_DOCUMENT_NODE',
+ \XML_DTD_NODE => 'XML_DTD_NODE',
+ \XML_ELEMENT_DECL_NODE => 'XML_ELEMENT_DECL_NODE',
+ \XML_ATTRIBUTE_DECL_NODE => 'XML_ATTRIBUTE_DECL_NODE',
+ \XML_ENTITY_DECL_NODE => 'XML_ENTITY_DECL_NODE',
+ \XML_NAMESPACE_DECL_NODE => 'XML_NAMESPACE_DECL_NODE',
+ ];
+
+ public static function castException(\DOMException $e, array $a, Stub $stub, $isNested)
+ {
+ $k = Caster::PREFIX_PROTECTED.'code';
+ if (isset($a[$k], self::ERROR_CODES[$a[$k]])) {
+ $a[$k] = new ConstStub(self::ERROR_CODES[$a[$k]], $a[$k]);
+ }
+
+ return $a;
+ }
+
+ public static function castLength($dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'length' => $dom->length,
+ ];
+
+ return $a;
+ }
+
+ public static function castImplementation($dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'Core' => '1.0',
+ Caster::PREFIX_VIRTUAL.'XML' => '2.0',
+ ];
+
+ return $a;
+ }
+
+ public static function castNode(\DOMNode $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'nodeName' => $dom->nodeName,
+ 'nodeValue' => new CutStub($dom->nodeValue),
+ 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType),
+ 'parentNode' => new CutStub($dom->parentNode),
+ 'childNodes' => $dom->childNodes,
+ 'firstChild' => new CutStub($dom->firstChild),
+ 'lastChild' => new CutStub($dom->lastChild),
+ 'previousSibling' => new CutStub($dom->previousSibling),
+ 'nextSibling' => new CutStub($dom->nextSibling),
+ 'attributes' => $dom->attributes,
+ 'ownerDocument' => new CutStub($dom->ownerDocument),
+ 'namespaceURI' => $dom->namespaceURI,
+ 'prefix' => $dom->prefix,
+ 'localName' => $dom->localName,
+ 'baseURI' => $dom->baseURI ? new LinkStub($dom->baseURI) : $dom->baseURI,
+ 'textContent' => new CutStub($dom->textContent),
+ ];
+
+ return $a;
+ }
+
+ public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'nodeName' => $dom->nodeName,
+ 'nodeValue' => new CutStub($dom->nodeValue),
+ 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType),
+ 'prefix' => $dom->prefix,
+ 'localName' => $dom->localName,
+ 'namespaceURI' => $dom->namespaceURI,
+ 'ownerDocument' => new CutStub($dom->ownerDocument),
+ 'parentNode' => new CutStub($dom->parentNode),
+ ];
+
+ return $a;
+ }
+
+ public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $a += [
+ 'doctype' => $dom->doctype,
+ 'implementation' => $dom->implementation,
+ 'documentElement' => new CutStub($dom->documentElement),
+ 'actualEncoding' => $dom->actualEncoding,
+ 'encoding' => $dom->encoding,
+ 'xmlEncoding' => $dom->xmlEncoding,
+ 'standalone' => $dom->standalone,
+ 'xmlStandalone' => $dom->xmlStandalone,
+ 'version' => $dom->version,
+ 'xmlVersion' => $dom->xmlVersion,
+ 'strictErrorChecking' => $dom->strictErrorChecking,
+ 'documentURI' => $dom->documentURI ? new LinkStub($dom->documentURI) : $dom->documentURI,
+ 'config' => $dom->config,
+ 'formatOutput' => $dom->formatOutput,
+ 'validateOnParse' => $dom->validateOnParse,
+ 'resolveExternals' => $dom->resolveExternals,
+ 'preserveWhiteSpace' => $dom->preserveWhiteSpace,
+ 'recover' => $dom->recover,
+ 'substituteEntities' => $dom->substituteEntities,
+ ];
+
+ if (!($filter & Caster::EXCLUDE_VERBOSE)) {
+ $formatOutput = $dom->formatOutput;
+ $dom->formatOutput = true;
+ $a += [Caster::PREFIX_VIRTUAL.'xml' => $dom->saveXML()];
+ $dom->formatOutput = $formatOutput;
+ }
+
+ return $a;
+ }
+
+ public static function castCharacterData(\DOMCharacterData $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'data' => $dom->data,
+ 'length' => $dom->length,
+ ];
+
+ return $a;
+ }
+
+ public static function castAttr(\DOMAttr $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'name' => $dom->name,
+ 'specified' => $dom->specified,
+ 'value' => $dom->value,
+ 'ownerElement' => $dom->ownerElement,
+ 'schemaTypeInfo' => $dom->schemaTypeInfo,
+ ];
+
+ return $a;
+ }
+
+ public static function castElement(\DOMElement $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'tagName' => $dom->tagName,
+ 'schemaTypeInfo' => $dom->schemaTypeInfo,
+ ];
+
+ return $a;
+ }
+
+ public static function castText(\DOMText $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'wholeText' => $dom->wholeText,
+ ];
+
+ return $a;
+ }
+
+ public static function castTypeinfo(\DOMTypeinfo $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'typeName' => $dom->typeName,
+ 'typeNamespace' => $dom->typeNamespace,
+ ];
+
+ return $a;
+ }
+
+ public static function castDomError(\DOMDomError $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'severity' => $dom->severity,
+ 'message' => $dom->message,
+ 'type' => $dom->type,
+ 'relatedException' => $dom->relatedException,
+ 'related_data' => $dom->related_data,
+ 'location' => $dom->location,
+ ];
+
+ return $a;
+ }
+
+ public static function castLocator(\DOMLocator $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'lineNumber' => $dom->lineNumber,
+ 'columnNumber' => $dom->columnNumber,
+ 'offset' => $dom->offset,
+ 'relatedNode' => $dom->relatedNode,
+ 'uri' => $dom->uri ? new LinkStub($dom->uri, $dom->lineNumber) : $dom->uri,
+ ];
+
+ return $a;
+ }
+
+ public static function castDocumentType(\DOMDocumentType $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'name' => $dom->name,
+ 'entities' => $dom->entities,
+ 'notations' => $dom->notations,
+ 'publicId' => $dom->publicId,
+ 'systemId' => $dom->systemId,
+ 'internalSubset' => $dom->internalSubset,
+ ];
+
+ return $a;
+ }
+
+ public static function castNotation(\DOMNotation $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'publicId' => $dom->publicId,
+ 'systemId' => $dom->systemId,
+ ];
+
+ return $a;
+ }
+
+ public static function castEntity(\DOMEntity $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'publicId' => $dom->publicId,
+ 'systemId' => $dom->systemId,
+ 'notationName' => $dom->notationName,
+ 'actualEncoding' => $dom->actualEncoding,
+ 'encoding' => $dom->encoding,
+ 'version' => $dom->version,
+ ];
+
+ return $a;
+ }
+
+ public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'target' => $dom->target,
+ 'data' => $dom->data,
+ ];
+
+ return $a;
+ }
+
+ public static function castXPath(\DOMXPath $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'document' => $dom->document,
+ ];
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/DateCaster.php b/vendor/symfony/var-dumper/Caster/DateCaster.php
new file mode 100644
index 000000000..171fbde55
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/DateCaster.php
@@ -0,0 +1,128 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts DateTimeInterface related classes to array representation.
+ *
+ * @author Dany Maillard
+ *
+ * @final since Symfony 4.4
+ */
+class DateCaster
+{
+ private const PERIOD_LIMIT = 3;
+
+ public static function castDateTime(\DateTimeInterface $d, array $a, Stub $stub, $isNested, $filter)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $location = $d->getTimezone()->getLocation();
+ $fromNow = (new \DateTime())->diff($d);
+
+ $title = $d->format('l, F j, Y')
+ ."\n".self::formatInterval($fromNow).' from now'
+ .($location ? ($d->format('I') ? "\nDST On" : "\nDST Off") : '')
+ ;
+
+ unset(
+ $a[Caster::PREFIX_DYNAMIC.'date'],
+ $a[Caster::PREFIX_DYNAMIC.'timezone'],
+ $a[Caster::PREFIX_DYNAMIC.'timezone_type']
+ );
+ $a[$prefix.'date'] = new ConstStub(self::formatDateTime($d, $location ? ' e (P)' : ' P'), $title);
+
+ $stub->class .= $d->format(' @U');
+
+ return $a;
+ }
+
+ public static function castInterval(\DateInterval $interval, array $a, Stub $stub, $isNested, $filter)
+ {
+ $now = new \DateTimeImmutable();
+ $numberOfSeconds = $now->add($interval)->getTimestamp() - $now->getTimestamp();
+ $title = number_format($numberOfSeconds, 0, '.', ' ').'s';
+
+ $i = [Caster::PREFIX_VIRTUAL.'interval' => new ConstStub(self::formatInterval($interval), $title)];
+
+ return $filter & Caster::EXCLUDE_VERBOSE ? $i : $i + $a;
+ }
+
+ private static function formatInterval(\DateInterval $i): string
+ {
+ $format = '%R ';
+
+ if (0 === $i->y && 0 === $i->m && ($i->h >= 24 || $i->i >= 60 || $i->s >= 60)) {
+ $i = date_diff($d = new \DateTime(), date_add(clone $d, $i)); // recalculate carry over points
+ $format .= 0 < $i->days ? '%ad ' : '';
+ } else {
+ $format .= ($i->y ? '%yy ' : '').($i->m ? '%mm ' : '').($i->d ? '%dd ' : '');
+ }
+
+ $format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, substr($i->f, 2)) : '';
+ $format = '%R ' === $format ? '0s' : $format;
+
+ return $i->format(rtrim($format));
+ }
+
+ public static function castTimeZone(\DateTimeZone $timeZone, array $a, Stub $stub, $isNested, $filter)
+ {
+ $location = $timeZone->getLocation();
+ $formatted = (new \DateTime('now', $timeZone))->format($location ? 'e (P)' : 'P');
+ $title = $location && \extension_loaded('intl') ? \Locale::getDisplayRegion('-'.$location['country_code']) : '';
+
+ $z = [Caster::PREFIX_VIRTUAL.'timezone' => new ConstStub($formatted, $title)];
+
+ return $filter & Caster::EXCLUDE_VERBOSE ? $z : $z + $a;
+ }
+
+ public static function castPeriod(\DatePeriod $p, array $a, Stub $stub, $isNested, $filter)
+ {
+ $dates = [];
+ if (\PHP_VERSION_ID >= 70107) { // see https://bugs.php.net/74639
+ foreach (clone $p as $i => $d) {
+ if (self::PERIOD_LIMIT === $i) {
+ $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
+ $dates[] = sprintf('%s more', ($end = $p->getEndDate())
+ ? ceil(($end->format('U.u') - $d->format('U.u')) / ((int) $now->add($p->getDateInterval())->format('U.u') - (int) $now->format('U.u')))
+ : $p->recurrences - $i
+ );
+ break;
+ }
+ $dates[] = sprintf('%s) %s', $i + 1, self::formatDateTime($d));
+ }
+ }
+
+ $period = sprintf(
+ 'every %s, from %s (%s) %s',
+ self::formatInterval($p->getDateInterval()),
+ self::formatDateTime($p->getStartDate()),
+ $p->include_start_date ? 'included' : 'excluded',
+ ($end = $p->getEndDate()) ? 'to '.self::formatDateTime($end) : 'recurring '.$p->recurrences.' time/s'
+ );
+
+ $p = [Caster::PREFIX_VIRTUAL.'period' => new ConstStub($period, implode("\n", $dates))];
+
+ return $filter & Caster::EXCLUDE_VERBOSE ? $p : $p + $a;
+ }
+
+ private static function formatDateTime(\DateTimeInterface $d, string $extra = ''): string
+ {
+ return $d->format('Y-m-d H:i:'.self::formatSeconds($d->format('s'), $d->format('u')).$extra);
+ }
+
+ private static function formatSeconds(string $s, string $us): string
+ {
+ return sprintf('%02d.%s', $s, 0 === ($len = \strlen($t = rtrim($us, '0'))) ? '0' : ($len <= 3 ? str_pad($t, 3, '0') : $us));
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/DoctrineCaster.php b/vendor/symfony/var-dumper/Caster/DoctrineCaster.php
new file mode 100644
index 000000000..7409508b0
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/DoctrineCaster.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Doctrine\Common\Proxy\Proxy as CommonProxy;
+use Doctrine\ORM\PersistentCollection;
+use Doctrine\ORM\Proxy\Proxy as OrmProxy;
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts Doctrine related classes to array representation.
+ *
+ * @author Nicolas Grekas
+ *
+ * @final since Symfony 4.4
+ */
+class DoctrineCaster
+{
+ public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, $isNested)
+ {
+ foreach (['__cloner__', '__initializer__'] as $k) {
+ if (\array_key_exists($k, $a)) {
+ unset($a[$k]);
+ ++$stub->cut;
+ }
+ }
+
+ return $a;
+ }
+
+ public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, $isNested)
+ {
+ foreach (['_entityPersister', '_identifier'] as $k) {
+ if (\array_key_exists($k = "\0Doctrine\\ORM\\Proxy\\Proxy\0".$k, $a)) {
+ unset($a[$k]);
+ ++$stub->cut;
+ }
+ }
+
+ return $a;
+ }
+
+ public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, $isNested)
+ {
+ foreach (['snapshot', 'association', 'typeClass'] as $k) {
+ if (\array_key_exists($k = "\0Doctrine\\ORM\\PersistentCollection\0".$k, $a)) {
+ $a[$k] = new CutStub($a[$k]);
+ }
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/DsCaster.php b/vendor/symfony/var-dumper/Caster/DsCaster.php
new file mode 100644
index 000000000..11423c9b2
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/DsCaster.php
@@ -0,0 +1,70 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Ds\Collection;
+use Ds\Map;
+use Ds\Pair;
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts Ds extension classes to array representation.
+ *
+ * @author Jáchym Toušek
+ *
+ * @final since Symfony 4.4
+ */
+class DsCaster
+{
+ public static function castCollection(Collection $c, array $a, Stub $stub, bool $isNested): array
+ {
+ $a[Caster::PREFIX_VIRTUAL.'count'] = $c->count();
+ $a[Caster::PREFIX_VIRTUAL.'capacity'] = $c->capacity();
+
+ if (!$c instanceof Map) {
+ $a += $c->toArray();
+ }
+
+ return $a;
+ }
+
+ public static function castMap(Map $c, array $a, Stub $stub, bool $isNested): array
+ {
+ foreach ($c as $k => $v) {
+ $a[] = new DsPairStub($k, $v);
+ }
+
+ return $a;
+ }
+
+ public static function castPair(Pair $c, array $a, Stub $stub, bool $isNested): array
+ {
+ foreach ($c->toArray() as $k => $v) {
+ $a[Caster::PREFIX_VIRTUAL.$k] = $v;
+ }
+
+ return $a;
+ }
+
+ public static function castPairStub(DsPairStub $c, array $a, Stub $stub, bool $isNested): array
+ {
+ if ($isNested) {
+ $stub->class = Pair::class;
+ $stub->value = null;
+ $stub->handle = 0;
+
+ $a = $c->value;
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/DsPairStub.php b/vendor/symfony/var-dumper/Caster/DsPairStub.php
new file mode 100644
index 000000000..a1dcc1561
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/DsPairStub.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * @author Nicolas Grekas
+ */
+class DsPairStub extends Stub
+{
+ public function __construct($key, $value)
+ {
+ $this->value = [
+ Caster::PREFIX_VIRTUAL.'key' => $key,
+ Caster::PREFIX_VIRTUAL.'value' => $value,
+ ];
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/EnumStub.php b/vendor/symfony/var-dumper/Caster/EnumStub.php
new file mode 100644
index 000000000..7a4e98a21
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/EnumStub.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Represents an enumeration of values.
+ *
+ * @author Nicolas Grekas
+ */
+class EnumStub extends Stub
+{
+ public $dumpKeys = true;
+
+ public function __construct(array $values, bool $dumpKeys = true)
+ {
+ $this->value = $values;
+ $this->dumpKeys = $dumpKeys;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ExceptionCaster.php b/vendor/symfony/var-dumper/Caster/ExceptionCaster.php
new file mode 100644
index 000000000..3ea17526c
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ExceptionCaster.php
@@ -0,0 +1,382 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
+use Symfony\Component\VarDumper\Cloner\Stub;
+use Symfony\Component\VarDumper\Exception\ThrowingCasterException;
+
+/**
+ * Casts common Exception classes to array representation.
+ *
+ * @author Nicolas Grekas
+ *
+ * @final since Symfony 4.4
+ */
+class ExceptionCaster
+{
+ public static $srcContext = 1;
+ public static $traceArgs = true;
+ public static $errorTypes = [
+ \E_DEPRECATED => 'E_DEPRECATED',
+ \E_USER_DEPRECATED => 'E_USER_DEPRECATED',
+ \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
+ \E_ERROR => 'E_ERROR',
+ \E_WARNING => 'E_WARNING',
+ \E_PARSE => 'E_PARSE',
+ \E_NOTICE => 'E_NOTICE',
+ \E_CORE_ERROR => 'E_CORE_ERROR',
+ \E_CORE_WARNING => 'E_CORE_WARNING',
+ \E_COMPILE_ERROR => 'E_COMPILE_ERROR',
+ \E_COMPILE_WARNING => 'E_COMPILE_WARNING',
+ \E_USER_ERROR => 'E_USER_ERROR',
+ \E_USER_WARNING => 'E_USER_WARNING',
+ \E_USER_NOTICE => 'E_USER_NOTICE',
+ \E_STRICT => 'E_STRICT',
+ ];
+
+ private static $framesCache = [];
+
+ public static function castError(\Error $e, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ return self::filterExceptionArray($stub->class, $a, "\0Error\0", $filter);
+ }
+
+ public static function castException(\Exception $e, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ return self::filterExceptionArray($stub->class, $a, "\0Exception\0", $filter);
+ }
+
+ public static function castErrorException(\ErrorException $e, array $a, Stub $stub, $isNested)
+ {
+ if (isset($a[$s = Caster::PREFIX_PROTECTED.'severity'], self::$errorTypes[$a[$s]])) {
+ $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]);
+ }
+
+ return $a;
+ }
+
+ public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, $isNested)
+ {
+ $trace = Caster::PREFIX_VIRTUAL.'trace';
+ $prefix = Caster::PREFIX_PROTECTED;
+ $xPrefix = "\0Exception\0";
+
+ if (isset($a[$xPrefix.'previous'], $a[$trace]) && $a[$xPrefix.'previous'] instanceof \Exception) {
+ $b = (array) $a[$xPrefix.'previous'];
+ $class = get_debug_type($a[$xPrefix.'previous']);
+ self::traceUnshift($b[$xPrefix.'trace'], $class, $b[$prefix.'file'], $b[$prefix.'line']);
+ $a[$trace] = new TraceStub($b[$xPrefix.'trace'], false, 0, -\count($a[$trace]->value));
+ }
+
+ unset($a[$xPrefix.'previous'], $a[$prefix.'code'], $a[$prefix.'file'], $a[$prefix.'line']);
+
+ return $a;
+ }
+
+ public static function castSilencedErrorContext(SilencedErrorContext $e, array $a, Stub $stub, $isNested)
+ {
+ $sPrefix = "\0".SilencedErrorContext::class."\0";
+
+ if (!isset($a[$s = $sPrefix.'severity'])) {
+ return $a;
+ }
+
+ if (isset(self::$errorTypes[$a[$s]])) {
+ $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]);
+ }
+
+ $trace = [[
+ 'file' => $a[$sPrefix.'file'],
+ 'line' => $a[$sPrefix.'line'],
+ ]];
+
+ if (isset($a[$sPrefix.'trace'])) {
+ $trace = array_merge($trace, $a[$sPrefix.'trace']);
+ }
+
+ unset($a[$sPrefix.'file'], $a[$sPrefix.'line'], $a[$sPrefix.'trace']);
+ $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs);
+
+ return $a;
+ }
+
+ public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $isNested)
+ {
+ if (!$isNested) {
+ return $a;
+ }
+ $stub->class = '';
+ $stub->handle = 0;
+ $frames = $trace->value;
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ $a = [];
+ $j = \count($frames);
+ if (0 > $i = $trace->sliceOffset) {
+ $i = max(0, $j + $i);
+ }
+ if (!isset($trace->value[$i])) {
+ return [];
+ }
+ $lastCall = isset($frames[$i]['function']) ? (isset($frames[$i]['class']) ? $frames[0]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : '';
+ $frames[] = ['function' => ''];
+ $collapse = false;
+
+ for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) {
+ $f = $frames[$i];
+ $call = isset($f['function']) ? (isset($f['class']) ? $f['class'].$f['type'] : '').$f['function'] : '???';
+
+ $frame = new FrameStub(
+ [
+ 'object' => $f['object'] ?? null,
+ 'class' => $f['class'] ?? null,
+ 'type' => $f['type'] ?? null,
+ 'function' => $f['function'] ?? null,
+ ] + $frames[$i - 1],
+ false,
+ true
+ );
+ $f = self::castFrameStub($frame, [], $frame, true);
+ if (isset($f[$prefix.'src'])) {
+ foreach ($f[$prefix.'src']->value as $label => $frame) {
+ if (0 === strpos($label, "\0~collapse=0")) {
+ if ($collapse) {
+ $label = substr_replace($label, '1', 11, 1);
+ } else {
+ $collapse = true;
+ }
+ }
+ $label = substr_replace($label, "title=Stack level $j.&", 2, 0);
+ }
+ $f = $frames[$i - 1];
+ if ($trace->keepArgs && !empty($f['args']) && $frame instanceof EnumStub) {
+ $frame->value['arguments'] = new ArgsStub($f['args'], $f['function'] ?? null, $f['class'] ?? null);
+ }
+ } elseif ('???' !== $lastCall) {
+ $label = new ClassStub($lastCall);
+ if (isset($label->attr['ellipsis'])) {
+ $label->attr['ellipsis'] += 2;
+ $label = substr_replace($prefix, "ellipsis-type=class&ellipsis={$label->attr['ellipsis']}&ellipsis-tail=1&title=Stack level $j.", 2, 0).$label->value.'()';
+ } else {
+ $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$label->value.'()';
+ }
+ } else {
+ $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$lastCall;
+ }
+ $a[substr_replace($label, sprintf('separator=%s&', $frame instanceof EnumStub ? ' ' : ':'), 2, 0)] = $frame;
+
+ $lastCall = $call;
+ }
+ if (null !== $trace->sliceLength) {
+ $a = \array_slice($a, 0, $trace->sliceLength, true);
+ }
+
+ return $a;
+ }
+
+ public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, $isNested)
+ {
+ if (!$isNested) {
+ return $a;
+ }
+ $f = $frame->value;
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ if (isset($f['file'], $f['line'])) {
+ $cacheKey = $f;
+ unset($cacheKey['object'], $cacheKey['args']);
+ $cacheKey[] = self::$srcContext;
+ $cacheKey = implode('-', $cacheKey);
+
+ if (isset(self::$framesCache[$cacheKey])) {
+ $a[$prefix.'src'] = self::$framesCache[$cacheKey];
+ } else {
+ if (preg_match('/\((\d+)\)(?:\([\da-f]{32}\))? : (?:eval\(\)\'d code|runtime-created function)$/', $f['file'], $match)) {
+ $f['file'] = substr($f['file'], 0, -\strlen($match[0]));
+ $f['line'] = (int) $match[1];
+ }
+ $src = $f['line'];
+ $srcKey = $f['file'];
+ $ellipsis = new LinkStub($srcKey, 0);
+ $srcAttr = 'collapse='.(int) $ellipsis->inVendor;
+ $ellipsisTail = $ellipsis->attr['ellipsis-tail'] ?? 0;
+ $ellipsis = $ellipsis->attr['ellipsis'] ?? 0;
+
+ if (file_exists($f['file']) && 0 <= self::$srcContext) {
+ if (!empty($f['class']) && (is_subclass_of($f['class'], 'Twig\Template') || is_subclass_of($f['class'], 'Twig_Template')) && method_exists($f['class'], 'getDebugInfo')) {
+ $template = $f['object'] ?? unserialize(sprintf('O:%d:"%s":0:{}', \strlen($f['class']), $f['class']));
+
+ $ellipsis = 0;
+ $templateSrc = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : '');
+ $templateInfo = $template->getDebugInfo();
+ if (isset($templateInfo[$f['line']])) {
+ if (!method_exists($template, 'getSourceContext') || !file_exists($templatePath = $template->getSourceContext()->getPath())) {
+ $templatePath = null;
+ }
+ if ($templateSrc) {
+ $src = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext, 'twig', $templatePath, $f);
+ $srcKey = ($templatePath ?: $template->getTemplateName()).':'.$templateInfo[$f['line']];
+ }
+ }
+ }
+ if ($srcKey == $f['file']) {
+ $src = self::extractSource(file_get_contents($f['file']), $f['line'], self::$srcContext, 'php', $f['file'], $f);
+ $srcKey .= ':'.$f['line'];
+ if ($ellipsis) {
+ $ellipsis += 1 + \strlen($f['line']);
+ }
+ }
+ $srcAttr .= sprintf('&separator= &file=%s&line=%d', rawurlencode($f['file']), $f['line']);
+ } else {
+ $srcAttr .= '&separator=:';
+ }
+ $srcAttr .= $ellipsis ? '&ellipsis-type=path&ellipsis='.$ellipsis.'&ellipsis-tail='.$ellipsisTail : '';
+ self::$framesCache[$cacheKey] = $a[$prefix.'src'] = new EnumStub(["\0~$srcAttr\0$srcKey" => $src]);
+ }
+ }
+
+ unset($a[$prefix.'args'], $a[$prefix.'line'], $a[$prefix.'file']);
+ if ($frame->inTraceStub) {
+ unset($a[$prefix.'class'], $a[$prefix.'type'], $a[$prefix.'function']);
+ }
+ foreach ($a as $k => $v) {
+ if (!$v) {
+ unset($a[$k]);
+ }
+ }
+ if ($frame->keepArgs && !empty($f['args'])) {
+ $a[$prefix.'arguments'] = new ArgsStub($f['args'], $f['function'], $f['class']);
+ }
+
+ return $a;
+ }
+
+ private static function filterExceptionArray(string $xClass, array $a, string $xPrefix, int $filter): array
+ {
+ if (isset($a[$xPrefix.'trace'])) {
+ $trace = $a[$xPrefix.'trace'];
+ unset($a[$xPrefix.'trace']); // Ensures the trace is always last
+ } else {
+ $trace = [];
+ }
+
+ if (!($filter & Caster::EXCLUDE_VERBOSE) && $trace) {
+ if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) {
+ self::traceUnshift($trace, $xClass, $a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']);
+ }
+ $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs);
+ }
+ if (empty($a[$xPrefix.'previous'])) {
+ unset($a[$xPrefix.'previous']);
+ }
+ unset($a[$xPrefix.'string'], $a[Caster::PREFIX_DYNAMIC.'xdebug_message'], $a[Caster::PREFIX_DYNAMIC.'__destructorException']);
+
+ if (isset($a[Caster::PREFIX_PROTECTED.'message']) && false !== strpos($a[Caster::PREFIX_PROTECTED.'message'], "@anonymous\0")) {
+ $a[Caster::PREFIX_PROTECTED.'message'] = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
+ return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
+ }, $a[Caster::PREFIX_PROTECTED.'message']);
+ }
+
+ if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) {
+ $a[Caster::PREFIX_PROTECTED.'file'] = new LinkStub($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']);
+ }
+
+ return $a;
+ }
+
+ private static function traceUnshift(array &$trace, ?string $class, string $file, int $line): void
+ {
+ if (isset($trace[0]['file'], $trace[0]['line']) && $trace[0]['file'] === $file && $trace[0]['line'] === $line) {
+ return;
+ }
+ array_unshift($trace, [
+ 'function' => $class ? 'new '.$class : null,
+ 'file' => $file,
+ 'line' => $line,
+ ]);
+ }
+
+ private static function extractSource(string $srcLines, int $line, int $srcContext, string $lang, ?string $file, array $frame): EnumStub
+ {
+ $srcLines = explode("\n", $srcLines);
+ $src = [];
+
+ for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) {
+ $src[] = ($srcLines[$i] ?? '')."\n";
+ }
+
+ if ($frame['function'] ?? false) {
+ $stub = new CutStub(new \stdClass());
+ $stub->class = (isset($frame['class']) ? $frame['class'].$frame['type'] : '').$frame['function'];
+ $stub->type = Stub::TYPE_OBJECT;
+ $stub->attr['cut_hash'] = true;
+ $stub->attr['file'] = $frame['file'];
+ $stub->attr['line'] = $frame['line'];
+
+ try {
+ $caller = isset($frame['class']) ? new \ReflectionMethod($frame['class'], $frame['function']) : new \ReflectionFunction($frame['function']);
+ $stub->class .= ReflectionCaster::getSignature(ReflectionCaster::castFunctionAbstract($caller, [], $stub, true, Caster::EXCLUDE_VERBOSE));
+
+ if ($f = $caller->getFileName()) {
+ $stub->attr['file'] = $f;
+ $stub->attr['line'] = $caller->getStartLine();
+ }
+ } catch (\ReflectionException $e) {
+ // ignore fake class/function
+ }
+
+ $srcLines = ["\0~separator=\0" => $stub];
+ } else {
+ $stub = null;
+ $srcLines = [];
+ }
+
+ $ltrim = 0;
+ do {
+ $pad = null;
+ for ($i = $srcContext << 1; $i >= 0; --$i) {
+ if (isset($src[$i][$ltrim]) && "\r" !== ($c = $src[$i][$ltrim]) && "\n" !== $c) {
+ if (null === $pad) {
+ $pad = $c;
+ }
+ if ((' ' !== $c && "\t" !== $c) || $pad !== $c) {
+ break;
+ }
+ }
+ }
+ ++$ltrim;
+ } while (0 > $i && null !== $pad);
+
+ --$ltrim;
+
+ foreach ($src as $i => $c) {
+ if ($ltrim) {
+ $c = isset($c[$ltrim]) && "\r" !== $c[$ltrim] ? substr($c, $ltrim) : ltrim($c, " \t");
+ }
+ $c = substr($c, 0, -1);
+ if ($i !== $srcContext) {
+ $c = new ConstStub('default', $c);
+ } else {
+ $c = new ConstStub($c, $stub ? 'in '.$stub->class : '');
+ if (null !== $file) {
+ $c->attr['file'] = $file;
+ $c->attr['line'] = $line;
+ }
+ }
+ $c->attr['lang'] = $lang;
+ $srcLines[sprintf("\0~separator=› &%d\0", $i + $line - $srcContext)] = $c;
+ }
+
+ return new EnumStub($srcLines);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/FrameStub.php b/vendor/symfony/var-dumper/Caster/FrameStub.php
new file mode 100644
index 000000000..878675528
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/FrameStub.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+/**
+ * Represents a single backtrace frame as returned by debug_backtrace() or Exception->getTrace().
+ *
+ * @author Nicolas Grekas
+ */
+class FrameStub extends EnumStub
+{
+ public $keepArgs;
+ public $inTraceStub;
+
+ public function __construct(array $frame, bool $keepArgs = true, bool $inTraceStub = false)
+ {
+ $this->value = $frame;
+ $this->keepArgs = $keepArgs;
+ $this->inTraceStub = $inTraceStub;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/GmpCaster.php b/vendor/symfony/var-dumper/Caster/GmpCaster.php
new file mode 100644
index 000000000..2b20e15dc
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/GmpCaster.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts GMP objects to array representation.
+ *
+ * @author Hamza Amrouche
+ * @author Nicolas Grekas
+ *
+ * @final since Symfony 4.4
+ */
+class GmpCaster
+{
+ public static function castGmp(\GMP $gmp, array $a, Stub $stub, $isNested, $filter): array
+ {
+ $a[Caster::PREFIX_VIRTUAL.'value'] = new ConstStub(gmp_strval($gmp), gmp_strval($gmp));
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ImagineCaster.php b/vendor/symfony/var-dumper/Caster/ImagineCaster.php
new file mode 100644
index 000000000..d1289da33
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ImagineCaster.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Imagine\Image\ImageInterface;
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * @author Grégoire Pineau
+ */
+final class ImagineCaster
+{
+ public static function castImage(ImageInterface $c, array $a, Stub $stub, bool $isNested): array
+ {
+ $imgData = $c->get('png');
+ if (\strlen($imgData) > 1 * 1000 * 1000) {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'image' => new ConstStub($c->getSize()),
+ ];
+ } else {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'image' => new ImgStub($imgData, 'image/png', $c->getSize()),
+ ];
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ImgStub.php b/vendor/symfony/var-dumper/Caster/ImgStub.php
new file mode 100644
index 000000000..05789fe33
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ImgStub.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+/**
+ * @author Grégoire Pineau
+ */
+class ImgStub extends ConstStub
+{
+ public function __construct(string $data, string $contentType, string $size)
+ {
+ $this->value = '';
+ $this->attr['img-data'] = $data;
+ $this->attr['img-size'] = $size;
+ $this->attr['content-type'] = $contentType;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/IntlCaster.php b/vendor/symfony/var-dumper/Caster/IntlCaster.php
new file mode 100644
index 000000000..d7099cb18
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/IntlCaster.php
@@ -0,0 +1,172 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * @author Nicolas Grekas
+ * @author Jan Schädlich
+ *
+ * @final since Symfony 4.4
+ */
+class IntlCaster
+{
+ public static function castMessageFormatter(\MessageFormatter $c, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(),
+ Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(),
+ ];
+
+ return self::castError($c, $a);
+ }
+
+ public static function castNumberFormatter(\NumberFormatter $c, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(),
+ Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(),
+ ];
+
+ if ($filter & Caster::EXCLUDE_VERBOSE) {
+ $stub->cut += 3;
+
+ return self::castError($c, $a);
+ }
+
+ $a += [
+ Caster::PREFIX_VIRTUAL.'attributes' => new EnumStub(
+ [
+ 'PARSE_INT_ONLY' => $c->getAttribute(\NumberFormatter::PARSE_INT_ONLY),
+ 'GROUPING_USED' => $c->getAttribute(\NumberFormatter::GROUPING_USED),
+ 'DECIMAL_ALWAYS_SHOWN' => $c->getAttribute(\NumberFormatter::DECIMAL_ALWAYS_SHOWN),
+ 'MAX_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_INTEGER_DIGITS),
+ 'MIN_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_INTEGER_DIGITS),
+ 'INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::INTEGER_DIGITS),
+ 'MAX_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_FRACTION_DIGITS),
+ 'MIN_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_FRACTION_DIGITS),
+ 'FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::FRACTION_DIGITS),
+ 'MULTIPLIER' => $c->getAttribute(\NumberFormatter::MULTIPLIER),
+ 'GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::GROUPING_SIZE),
+ 'ROUNDING_MODE' => $c->getAttribute(\NumberFormatter::ROUNDING_MODE),
+ 'ROUNDING_INCREMENT' => $c->getAttribute(\NumberFormatter::ROUNDING_INCREMENT),
+ 'FORMAT_WIDTH' => $c->getAttribute(\NumberFormatter::FORMAT_WIDTH),
+ 'PADDING_POSITION' => $c->getAttribute(\NumberFormatter::PADDING_POSITION),
+ 'SECONDARY_GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::SECONDARY_GROUPING_SIZE),
+ 'SIGNIFICANT_DIGITS_USED' => $c->getAttribute(\NumberFormatter::SIGNIFICANT_DIGITS_USED),
+ 'MIN_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_SIGNIFICANT_DIGITS),
+ 'MAX_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_SIGNIFICANT_DIGITS),
+ 'LENIENT_PARSE' => $c->getAttribute(\NumberFormatter::LENIENT_PARSE),
+ ]
+ ),
+ Caster::PREFIX_VIRTUAL.'text_attributes' => new EnumStub(
+ [
+ 'POSITIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_PREFIX),
+ 'POSITIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_SUFFIX),
+ 'NEGATIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_PREFIX),
+ 'NEGATIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_SUFFIX),
+ 'PADDING_CHARACTER' => $c->getTextAttribute(\NumberFormatter::PADDING_CHARACTER),
+ 'CURRENCY_CODE' => $c->getTextAttribute(\NumberFormatter::CURRENCY_CODE),
+ 'DEFAULT_RULESET' => $c->getTextAttribute(\NumberFormatter::DEFAULT_RULESET),
+ 'PUBLIC_RULESETS' => $c->getTextAttribute(\NumberFormatter::PUBLIC_RULESETS),
+ ]
+ ),
+ Caster::PREFIX_VIRTUAL.'symbols' => new EnumStub(
+ [
+ 'DECIMAL_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL),
+ 'GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL),
+ 'PATTERN_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::PATTERN_SEPARATOR_SYMBOL),
+ 'PERCENT_SYMBOL' => $c->getSymbol(\NumberFormatter::PERCENT_SYMBOL),
+ 'ZERO_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::ZERO_DIGIT_SYMBOL),
+ 'DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::DIGIT_SYMBOL),
+ 'MINUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::MINUS_SIGN_SYMBOL),
+ 'PLUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::PLUS_SIGN_SYMBOL),
+ 'CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::CURRENCY_SYMBOL),
+ 'INTL_CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::INTL_CURRENCY_SYMBOL),
+ 'MONETARY_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL),
+ 'EXPONENTIAL_SYMBOL' => $c->getSymbol(\NumberFormatter::EXPONENTIAL_SYMBOL),
+ 'PERMILL_SYMBOL' => $c->getSymbol(\NumberFormatter::PERMILL_SYMBOL),
+ 'PAD_ESCAPE_SYMBOL' => $c->getSymbol(\NumberFormatter::PAD_ESCAPE_SYMBOL),
+ 'INFINITY_SYMBOL' => $c->getSymbol(\NumberFormatter::INFINITY_SYMBOL),
+ 'NAN_SYMBOL' => $c->getSymbol(\NumberFormatter::NAN_SYMBOL),
+ 'SIGNIFICANT_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL),
+ 'MONETARY_GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL),
+ ]
+ ),
+ ];
+
+ return self::castError($c, $a);
+ }
+
+ public static function castIntlTimeZone(\IntlTimeZone $c, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'display_name' => $c->getDisplayName(),
+ Caster::PREFIX_VIRTUAL.'id' => $c->getID(),
+ Caster::PREFIX_VIRTUAL.'raw_offset' => $c->getRawOffset(),
+ ];
+
+ if ($c->useDaylightTime()) {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'dst_savings' => $c->getDSTSavings(),
+ ];
+ }
+
+ return self::castError($c, $a);
+ }
+
+ public static function castIntlCalendar(\IntlCalendar $c, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'type' => $c->getType(),
+ Caster::PREFIX_VIRTUAL.'first_day_of_week' => $c->getFirstDayOfWeek(),
+ Caster::PREFIX_VIRTUAL.'minimal_days_in_first_week' => $c->getMinimalDaysInFirstWeek(),
+ Caster::PREFIX_VIRTUAL.'repeated_wall_time_option' => $c->getRepeatedWallTimeOption(),
+ Caster::PREFIX_VIRTUAL.'skipped_wall_time_option' => $c->getSkippedWallTimeOption(),
+ Caster::PREFIX_VIRTUAL.'time' => $c->getTime(),
+ Caster::PREFIX_VIRTUAL.'in_daylight_time' => $c->inDaylightTime(),
+ Caster::PREFIX_VIRTUAL.'is_lenient' => $c->isLenient(),
+ Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(),
+ ];
+
+ return self::castError($c, $a);
+ }
+
+ public static function castIntlDateFormatter(\IntlDateFormatter $c, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(),
+ Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(),
+ Caster::PREFIX_VIRTUAL.'calendar' => $c->getCalendar(),
+ Caster::PREFIX_VIRTUAL.'time_zone_id' => $c->getTimeZoneId(),
+ Caster::PREFIX_VIRTUAL.'time_type' => $c->getTimeType(),
+ Caster::PREFIX_VIRTUAL.'date_type' => $c->getDateType(),
+ Caster::PREFIX_VIRTUAL.'calendar_object' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getCalendarObject()) : $c->getCalendarObject(),
+ Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(),
+ ];
+
+ return self::castError($c, $a);
+ }
+
+ private static function castError($c, array $a): array
+ {
+ if ($errorCode = $c->getErrorCode()) {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'error_code' => $errorCode,
+ Caster::PREFIX_VIRTUAL.'error_message' => $c->getErrorMessage(),
+ ];
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/LinkStub.php b/vendor/symfony/var-dumper/Caster/LinkStub.php
new file mode 100644
index 000000000..6360716d7
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/LinkStub.php
@@ -0,0 +1,108 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+/**
+ * Represents a file or a URL.
+ *
+ * @author Nicolas Grekas
+ */
+class LinkStub extends ConstStub
+{
+ public $inVendor = false;
+
+ private static $vendorRoots;
+ private static $composerRoots;
+
+ public function __construct($label, int $line = 0, $href = null)
+ {
+ $this->value = $label;
+
+ if (null === $href) {
+ $href = $label;
+ }
+ if (!\is_string($href)) {
+ return;
+ }
+ if (0 === strpos($href, 'file://')) {
+ if ($href === $label) {
+ $label = substr($label, 7);
+ }
+ $href = substr($href, 7);
+ } elseif (false !== strpos($href, '://')) {
+ $this->attr['href'] = $href;
+
+ return;
+ }
+ if (!file_exists($href)) {
+ return;
+ }
+ if ($line) {
+ $this->attr['line'] = $line;
+ }
+ if ($label !== $this->attr['file'] = realpath($href) ?: $href) {
+ return;
+ }
+ if ($composerRoot = $this->getComposerRoot($href, $this->inVendor)) {
+ $this->attr['ellipsis'] = \strlen($href) - \strlen($composerRoot) + 1;
+ $this->attr['ellipsis-type'] = 'path';
+ $this->attr['ellipsis-tail'] = 1 + ($this->inVendor ? 2 + \strlen(implode('', \array_slice(explode(\DIRECTORY_SEPARATOR, substr($href, 1 - $this->attr['ellipsis'])), 0, 2))) : 0);
+ } elseif (3 < \count($ellipsis = explode(\DIRECTORY_SEPARATOR, $href))) {
+ $this->attr['ellipsis'] = 2 + \strlen(implode('', \array_slice($ellipsis, -2)));
+ $this->attr['ellipsis-type'] = 'path';
+ $this->attr['ellipsis-tail'] = 1;
+ }
+ }
+
+ private function getComposerRoot(string $file, bool &$inVendor)
+ {
+ if (null === self::$vendorRoots) {
+ self::$vendorRoots = [];
+
+ foreach (get_declared_classes() as $class) {
+ if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
+ $r = new \ReflectionClass($class);
+ $v = \dirname($r->getFileName(), 2);
+ if (file_exists($v.'/composer/installed.json')) {
+ self::$vendorRoots[] = $v.\DIRECTORY_SEPARATOR;
+ }
+ }
+ }
+ }
+ $inVendor = false;
+
+ if (isset(self::$composerRoots[$dir = \dirname($file)])) {
+ return self::$composerRoots[$dir];
+ }
+
+ foreach (self::$vendorRoots as $root) {
+ if ($inVendor = 0 === strpos($file, $root)) {
+ return $root;
+ }
+ }
+
+ $parent = $dir;
+ while (!@file_exists($parent.'/composer.json')) {
+ if (!@file_exists($parent)) {
+ // open_basedir restriction in effect
+ break;
+ }
+ if ($parent === \dirname($parent)) {
+ return self::$composerRoots[$dir] = false;
+ }
+
+ $parent = \dirname($parent);
+ }
+
+ return self::$composerRoots[$dir] = $parent.\DIRECTORY_SEPARATOR;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/MemcachedCaster.php b/vendor/symfony/var-dumper/Caster/MemcachedCaster.php
new file mode 100644
index 000000000..942eecb11
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/MemcachedCaster.php
@@ -0,0 +1,81 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * @author Jan Schädlich
+ *
+ * @final since Symfony 4.4
+ */
+class MemcachedCaster
+{
+ private static $optionConstants;
+ private static $defaultOptions;
+
+ public static function castMemcached(\Memcached $c, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'servers' => $c->getServerList(),
+ Caster::PREFIX_VIRTUAL.'options' => new EnumStub(
+ self::getNonDefaultOptions($c)
+ ),
+ ];
+
+ return $a;
+ }
+
+ private static function getNonDefaultOptions(\Memcached $c): array
+ {
+ self::$defaultOptions = self::$defaultOptions ?? self::discoverDefaultOptions();
+ self::$optionConstants = self::$optionConstants ?? self::getOptionConstants();
+
+ $nonDefaultOptions = [];
+ foreach (self::$optionConstants as $constantKey => $value) {
+ if (self::$defaultOptions[$constantKey] !== $option = $c->getOption($value)) {
+ $nonDefaultOptions[$constantKey] = $option;
+ }
+ }
+
+ return $nonDefaultOptions;
+ }
+
+ private static function discoverDefaultOptions(): array
+ {
+ $defaultMemcached = new \Memcached();
+ $defaultMemcached->addServer('127.0.0.1', 11211);
+
+ $defaultOptions = [];
+ self::$optionConstants = self::$optionConstants ?? self::getOptionConstants();
+
+ foreach (self::$optionConstants as $constantKey => $value) {
+ $defaultOptions[$constantKey] = $defaultMemcached->getOption($value);
+ }
+
+ return $defaultOptions;
+ }
+
+ private static function getOptionConstants(): array
+ {
+ $reflectedMemcached = new \ReflectionClass(\Memcached::class);
+
+ $optionConstants = [];
+ foreach ($reflectedMemcached->getConstants() as $constantKey => $value) {
+ if (0 === strpos($constantKey, 'OPT_')) {
+ $optionConstants[$constantKey] = $value;
+ }
+ }
+
+ return $optionConstants;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/PdoCaster.php b/vendor/symfony/var-dumper/Caster/PdoCaster.php
new file mode 100644
index 000000000..47b0a62b7
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/PdoCaster.php
@@ -0,0 +1,122 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts PDO related classes to array representation.
+ *
+ * @author Nicolas Grekas
+ *
+ * @final since Symfony 4.4
+ */
+class PdoCaster
+{
+ private const PDO_ATTRIBUTES = [
+ 'CASE' => [
+ \PDO::CASE_LOWER => 'LOWER',
+ \PDO::CASE_NATURAL => 'NATURAL',
+ \PDO::CASE_UPPER => 'UPPER',
+ ],
+ 'ERRMODE' => [
+ \PDO::ERRMODE_SILENT => 'SILENT',
+ \PDO::ERRMODE_WARNING => 'WARNING',
+ \PDO::ERRMODE_EXCEPTION => 'EXCEPTION',
+ ],
+ 'TIMEOUT',
+ 'PREFETCH',
+ 'AUTOCOMMIT',
+ 'PERSISTENT',
+ 'DRIVER_NAME',
+ 'SERVER_INFO',
+ 'ORACLE_NULLS' => [
+ \PDO::NULL_NATURAL => 'NATURAL',
+ \PDO::NULL_EMPTY_STRING => 'EMPTY_STRING',
+ \PDO::NULL_TO_STRING => 'TO_STRING',
+ ],
+ 'CLIENT_VERSION',
+ 'SERVER_VERSION',
+ 'STATEMENT_CLASS',
+ 'EMULATE_PREPARES',
+ 'CONNECTION_STATUS',
+ 'STRINGIFY_FETCHES',
+ 'DEFAULT_FETCH_MODE' => [
+ \PDO::FETCH_ASSOC => 'ASSOC',
+ \PDO::FETCH_BOTH => 'BOTH',
+ \PDO::FETCH_LAZY => 'LAZY',
+ \PDO::FETCH_NUM => 'NUM',
+ \PDO::FETCH_OBJ => 'OBJ',
+ ],
+ ];
+
+ public static function castPdo(\PDO $c, array $a, Stub $stub, $isNested)
+ {
+ $attr = [];
+ $errmode = $c->getAttribute(\PDO::ATTR_ERRMODE);
+ $c->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+
+ foreach (self::PDO_ATTRIBUTES as $k => $v) {
+ if (!isset($k[0])) {
+ $k = $v;
+ $v = [];
+ }
+
+ try {
+ $attr[$k] = 'ERRMODE' === $k ? $errmode : $c->getAttribute(\constant('PDO::ATTR_'.$k));
+ if ($v && isset($v[$attr[$k]])) {
+ $attr[$k] = new ConstStub($v[$attr[$k]], $attr[$k]);
+ }
+ } catch (\Exception $e) {
+ }
+ }
+ if (isset($attr[$k = 'STATEMENT_CLASS'][1])) {
+ if ($attr[$k][1]) {
+ $attr[$k][1] = new ArgsStub($attr[$k][1], '__construct', $attr[$k][0]);
+ }
+ $attr[$k][0] = new ClassStub($attr[$k][0]);
+ }
+
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $a += [
+ $prefix.'inTransaction' => method_exists($c, 'inTransaction'),
+ $prefix.'errorInfo' => $c->errorInfo(),
+ $prefix.'attributes' => new EnumStub($attr),
+ ];
+
+ if ($a[$prefix.'inTransaction']) {
+ $a[$prefix.'inTransaction'] = $c->inTransaction();
+ } else {
+ unset($a[$prefix.'inTransaction']);
+ }
+
+ if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) {
+ unset($a[$prefix.'errorInfo']);
+ }
+
+ $c->setAttribute(\PDO::ATTR_ERRMODE, $errmode);
+
+ return $a;
+ }
+
+ public static function castPdoStatement(\PDOStatement $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $a[$prefix.'errorInfo'] = $c->errorInfo();
+
+ if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) {
+ unset($a[$prefix.'errorInfo']);
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/PgSqlCaster.php b/vendor/symfony/var-dumper/Caster/PgSqlCaster.php
new file mode 100644
index 000000000..3097c5184
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/PgSqlCaster.php
@@ -0,0 +1,156 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts pqsql resources to array representation.
+ *
+ * @author Nicolas Grekas
+ *
+ * @final since Symfony 4.4
+ */
+class PgSqlCaster
+{
+ private const PARAM_CODES = [
+ 'server_encoding',
+ 'client_encoding',
+ 'is_superuser',
+ 'session_authorization',
+ 'DateStyle',
+ 'TimeZone',
+ 'IntervalStyle',
+ 'integer_datetimes',
+ 'application_name',
+ 'standard_conforming_strings',
+ ];
+
+ private const TRANSACTION_STATUS = [
+ \PGSQL_TRANSACTION_IDLE => 'PGSQL_TRANSACTION_IDLE',
+ \PGSQL_TRANSACTION_ACTIVE => 'PGSQL_TRANSACTION_ACTIVE',
+ \PGSQL_TRANSACTION_INTRANS => 'PGSQL_TRANSACTION_INTRANS',
+ \PGSQL_TRANSACTION_INERROR => 'PGSQL_TRANSACTION_INERROR',
+ \PGSQL_TRANSACTION_UNKNOWN => 'PGSQL_TRANSACTION_UNKNOWN',
+ ];
+
+ private const RESULT_STATUS = [
+ \PGSQL_EMPTY_QUERY => 'PGSQL_EMPTY_QUERY',
+ \PGSQL_COMMAND_OK => 'PGSQL_COMMAND_OK',
+ \PGSQL_TUPLES_OK => 'PGSQL_TUPLES_OK',
+ \PGSQL_COPY_OUT => 'PGSQL_COPY_OUT',
+ \PGSQL_COPY_IN => 'PGSQL_COPY_IN',
+ \PGSQL_BAD_RESPONSE => 'PGSQL_BAD_RESPONSE',
+ \PGSQL_NONFATAL_ERROR => 'PGSQL_NONFATAL_ERROR',
+ \PGSQL_FATAL_ERROR => 'PGSQL_FATAL_ERROR',
+ ];
+
+ private const DIAG_CODES = [
+ 'severity' => \PGSQL_DIAG_SEVERITY,
+ 'sqlstate' => \PGSQL_DIAG_SQLSTATE,
+ 'message' => \PGSQL_DIAG_MESSAGE_PRIMARY,
+ 'detail' => \PGSQL_DIAG_MESSAGE_DETAIL,
+ 'hint' => \PGSQL_DIAG_MESSAGE_HINT,
+ 'statement position' => \PGSQL_DIAG_STATEMENT_POSITION,
+ 'internal position' => \PGSQL_DIAG_INTERNAL_POSITION,
+ 'internal query' => \PGSQL_DIAG_INTERNAL_QUERY,
+ 'context' => \PGSQL_DIAG_CONTEXT,
+ 'file' => \PGSQL_DIAG_SOURCE_FILE,
+ 'line' => \PGSQL_DIAG_SOURCE_LINE,
+ 'function' => \PGSQL_DIAG_SOURCE_FUNCTION,
+ ];
+
+ public static function castLargeObject($lo, array $a, Stub $stub, $isNested)
+ {
+ $a['seek position'] = pg_lo_tell($lo);
+
+ return $a;
+ }
+
+ public static function castLink($link, array $a, Stub $stub, $isNested)
+ {
+ $a['status'] = pg_connection_status($link);
+ $a['status'] = new ConstStub(\PGSQL_CONNECTION_OK === $a['status'] ? 'PGSQL_CONNECTION_OK' : 'PGSQL_CONNECTION_BAD', $a['status']);
+ $a['busy'] = pg_connection_busy($link);
+
+ $a['transaction'] = pg_transaction_status($link);
+ if (isset(self::TRANSACTION_STATUS[$a['transaction']])) {
+ $a['transaction'] = new ConstStub(self::TRANSACTION_STATUS[$a['transaction']], $a['transaction']);
+ }
+
+ $a['pid'] = pg_get_pid($link);
+ $a['last error'] = pg_last_error($link);
+ $a['last notice'] = pg_last_notice($link);
+ $a['host'] = pg_host($link);
+ $a['port'] = pg_port($link);
+ $a['dbname'] = pg_dbname($link);
+ $a['options'] = pg_options($link);
+ $a['version'] = pg_version($link);
+
+ foreach (self::PARAM_CODES as $v) {
+ if (false !== $s = pg_parameter_status($link, $v)) {
+ $a['param'][$v] = $s;
+ }
+ }
+
+ $a['param']['client_encoding'] = pg_client_encoding($link);
+ $a['param'] = new EnumStub($a['param']);
+
+ return $a;
+ }
+
+ public static function castResult($result, array $a, Stub $stub, $isNested)
+ {
+ $a['num rows'] = pg_num_rows($result);
+ $a['status'] = pg_result_status($result);
+ if (isset(self::RESULT_STATUS[$a['status']])) {
+ $a['status'] = new ConstStub(self::RESULT_STATUS[$a['status']], $a['status']);
+ }
+ $a['command-completion tag'] = pg_result_status($result, \PGSQL_STATUS_STRING);
+
+ if (-1 === $a['num rows']) {
+ foreach (self::DIAG_CODES as $k => $v) {
+ $a['error'][$k] = pg_result_error_field($result, $v);
+ }
+ }
+
+ $a['affected rows'] = pg_affected_rows($result);
+ $a['last OID'] = pg_last_oid($result);
+
+ $fields = pg_num_fields($result);
+
+ for ($i = 0; $i < $fields; ++$i) {
+ $field = [
+ 'name' => pg_field_name($result, $i),
+ 'table' => sprintf('%s (OID: %s)', pg_field_table($result, $i), pg_field_table($result, $i, true)),
+ 'type' => sprintf('%s (OID: %s)', pg_field_type($result, $i), pg_field_type_oid($result, $i)),
+ 'nullable' => (bool) pg_field_is_null($result, $i),
+ 'storage' => pg_field_size($result, $i).' bytes',
+ 'display' => pg_field_prtlen($result, $i).' chars',
+ ];
+ if (' (OID: )' === $field['table']) {
+ $field['table'] = null;
+ }
+ if ('-1 bytes' === $field['storage']) {
+ $field['storage'] = 'variable size';
+ } elseif ('1 bytes' === $field['storage']) {
+ $field['storage'] = '1 byte';
+ }
+ if ('1 chars' === $field['display']) {
+ $field['display'] = '1 char';
+ }
+ $a['fields'][] = new EnumStub($field);
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php b/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php
new file mode 100644
index 000000000..ec02f8137
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use ProxyManager\Proxy\ProxyInterface;
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @final since Symfony 4.4
+ */
+class ProxyManagerCaster
+{
+ public static function castProxy(ProxyInterface $c, array $a, Stub $stub, $isNested)
+ {
+ if ($parent = get_parent_class($c)) {
+ $stub->class .= ' - '.$parent;
+ }
+ $stub->class .= '@proxy';
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/RedisCaster.php b/vendor/symfony/var-dumper/Caster/RedisCaster.php
new file mode 100644
index 000000000..bd877cb3e
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/RedisCaster.php
@@ -0,0 +1,152 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts Redis class from ext-redis to array representation.
+ *
+ * @author Nicolas Grekas
+ *
+ * @final since Symfony 4.4
+ */
+class RedisCaster
+{
+ private const SERIALIZERS = [
+ \Redis::SERIALIZER_NONE => 'NONE',
+ \Redis::SERIALIZER_PHP => 'PHP',
+ 2 => 'IGBINARY', // Optional Redis::SERIALIZER_IGBINARY
+ ];
+
+ private const MODES = [
+ \Redis::ATOMIC => 'ATOMIC',
+ \Redis::MULTI => 'MULTI',
+ \Redis::PIPELINE => 'PIPELINE',
+ ];
+
+ private const COMPRESSION_MODES = [
+ 0 => 'NONE', // Redis::COMPRESSION_NONE
+ 1 => 'LZF', // Redis::COMPRESSION_LZF
+ ];
+
+ private const FAILOVER_OPTIONS = [
+ \RedisCluster::FAILOVER_NONE => 'NONE',
+ \RedisCluster::FAILOVER_ERROR => 'ERROR',
+ \RedisCluster::FAILOVER_DISTRIBUTE => 'DISTRIBUTE',
+ \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES => 'DISTRIBUTE_SLAVES',
+ ];
+
+ public static function castRedis(\Redis $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ if (!$connected = $c->isConnected()) {
+ return $a + [
+ $prefix.'isConnected' => $connected,
+ ];
+ }
+
+ $mode = $c->getMode();
+
+ return $a + [
+ $prefix.'isConnected' => $connected,
+ $prefix.'host' => $c->getHost(),
+ $prefix.'port' => $c->getPort(),
+ $prefix.'auth' => $c->getAuth(),
+ $prefix.'mode' => isset(self::MODES[$mode]) ? new ConstStub(self::MODES[$mode], $mode) : $mode,
+ $prefix.'dbNum' => $c->getDbNum(),
+ $prefix.'timeout' => $c->getTimeout(),
+ $prefix.'lastError' => $c->getLastError(),
+ $prefix.'persistentId' => $c->getPersistentID(),
+ $prefix.'options' => self::getRedisOptions($c),
+ ];
+ }
+
+ public static function castRedisArray(\RedisArray $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ return $a + [
+ $prefix.'hosts' => $c->_hosts(),
+ $prefix.'function' => ClassStub::wrapCallable($c->_function()),
+ $prefix.'lastError' => $c->getLastError(),
+ $prefix.'options' => self::getRedisOptions($c),
+ ];
+ }
+
+ public static function castRedisCluster(\RedisCluster $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $failover = $c->getOption(\RedisCluster::OPT_SLAVE_FAILOVER);
+
+ $a += [
+ $prefix.'_masters' => $c->_masters(),
+ $prefix.'_redir' => $c->_redir(),
+ $prefix.'mode' => new ConstStub($c->getMode() ? 'MULTI' : 'ATOMIC', $c->getMode()),
+ $prefix.'lastError' => $c->getLastError(),
+ $prefix.'options' => self::getRedisOptions($c, [
+ 'SLAVE_FAILOVER' => isset(self::FAILOVER_OPTIONS[$failover]) ? new ConstStub(self::FAILOVER_OPTIONS[$failover], $failover) : $failover,
+ ]),
+ ];
+
+ return $a;
+ }
+
+ /**
+ * @param \Redis|\RedisArray|\RedisCluster $redis
+ */
+ private static function getRedisOptions($redis, array $options = []): EnumStub
+ {
+ $serializer = $redis->getOption(\Redis::OPT_SERIALIZER);
+ if (\is_array($serializer)) {
+ foreach ($serializer as &$v) {
+ if (isset(self::SERIALIZERS[$v])) {
+ $v = new ConstStub(self::SERIALIZERS[$v], $v);
+ }
+ }
+ } elseif (isset(self::SERIALIZERS[$serializer])) {
+ $serializer = new ConstStub(self::SERIALIZERS[$serializer], $serializer);
+ }
+
+ $compression = \defined('Redis::OPT_COMPRESSION') ? $redis->getOption(\Redis::OPT_COMPRESSION) : 0;
+ if (\is_array($compression)) {
+ foreach ($compression as &$v) {
+ if (isset(self::COMPRESSION_MODES[$v])) {
+ $v = new ConstStub(self::COMPRESSION_MODES[$v], $v);
+ }
+ }
+ } elseif (isset(self::COMPRESSION_MODES[$compression])) {
+ $compression = new ConstStub(self::COMPRESSION_MODES[$compression], $compression);
+ }
+
+ $retry = \defined('Redis::OPT_SCAN') ? $redis->getOption(\Redis::OPT_SCAN) : 0;
+ if (\is_array($retry)) {
+ foreach ($retry as &$v) {
+ $v = new ConstStub($v ? 'RETRY' : 'NORETRY', $v);
+ }
+ } else {
+ $retry = new ConstStub($retry ? 'RETRY' : 'NORETRY', $retry);
+ }
+
+ $options += [
+ 'TCP_KEEPALIVE' => \defined('Redis::OPT_TCP_KEEPALIVE') ? $redis->getOption(\Redis::OPT_TCP_KEEPALIVE) : 0,
+ 'READ_TIMEOUT' => $redis->getOption(\Redis::OPT_READ_TIMEOUT),
+ 'COMPRESSION' => $compression,
+ 'SERIALIZER' => $serializer,
+ 'PREFIX' => $redis->getOption(\Redis::OPT_PREFIX),
+ 'SCAN' => $retry,
+ ];
+
+ return new EnumStub($options);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ReflectionCaster.php b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php
new file mode 100644
index 000000000..95c1dbf6f
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php
@@ -0,0 +1,401 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts Reflector related classes to array representation.
+ *
+ * @author Nicolas Grekas
+ *
+ * @final since Symfony 4.4
+ */
+class ReflectionCaster
+{
+ public const UNSET_CLOSURE_FILE_INFO = ['Closure' => __CLASS__.'::unsetClosureFileInfo'];
+
+ private const EXTRA_MAP = [
+ 'docComment' => 'getDocComment',
+ 'extension' => 'getExtensionName',
+ 'isDisabled' => 'isDisabled',
+ 'isDeprecated' => 'isDeprecated',
+ 'isInternal' => 'isInternal',
+ 'isUserDefined' => 'isUserDefined',
+ 'isGenerator' => 'isGenerator',
+ 'isVariadic' => 'isVariadic',
+ ];
+
+ public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $c = new \ReflectionFunction($c);
+
+ $a = static::castFunctionAbstract($c, $a, $stub, $isNested, $filter);
+
+ if (false === strpos($c->name, '{closure}')) {
+ $stub->class = isset($a[$prefix.'class']) ? $a[$prefix.'class']->value.'::'.$c->name : $c->name;
+ unset($a[$prefix.'class']);
+ }
+ unset($a[$prefix.'extra']);
+
+ $stub->class .= self::getSignature($a);
+
+ if ($f = $c->getFileName()) {
+ $stub->attr['file'] = $f;
+ $stub->attr['line'] = $c->getStartLine();
+ }
+
+ unset($a[$prefix.'parameters']);
+
+ if ($filter & Caster::EXCLUDE_VERBOSE) {
+ $stub->cut += ($c->getFileName() ? 2 : 0) + \count($a);
+
+ return [];
+ }
+
+ if ($f) {
+ $a[$prefix.'file'] = new LinkStub($f, $c->getStartLine());
+ $a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine();
+ }
+
+ return $a;
+ }
+
+ public static function unsetClosureFileInfo(\Closure $c, array $a)
+ {
+ unset($a[Caster::PREFIX_VIRTUAL.'file'], $a[Caster::PREFIX_VIRTUAL.'line']);
+
+ return $a;
+ }
+
+ public static function castGenerator(\Generator $c, array $a, Stub $stub, $isNested)
+ {
+ // Cannot create ReflectionGenerator based on a terminated Generator
+ try {
+ $reflectionGenerator = new \ReflectionGenerator($c);
+ } catch (\Exception $e) {
+ $a[Caster::PREFIX_VIRTUAL.'closed'] = true;
+
+ return $a;
+ }
+
+ return self::castReflectionGenerator($reflectionGenerator, $a, $stub, $isNested);
+ }
+
+ public static function castType(\ReflectionType $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ if ($c instanceof \ReflectionNamedType || \PHP_VERSION_ID < 80000) {
+ $a += [
+ $prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : (string) $c,
+ $prefix.'allowsNull' => $c->allowsNull(),
+ $prefix.'isBuiltin' => $c->isBuiltin(),
+ ];
+ } elseif ($c instanceof \ReflectionUnionType) {
+ $a[$prefix.'allowsNull'] = $c->allowsNull();
+ self::addMap($a, $c, [
+ 'types' => 'getTypes',
+ ]);
+ } else {
+ $a[$prefix.'allowsNull'] = $c->allowsNull();
+ }
+
+ return $a;
+ }
+
+ public static function castReflectionGenerator(\ReflectionGenerator $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ if ($c->getThis()) {
+ $a[$prefix.'this'] = new CutStub($c->getThis());
+ }
+ $function = $c->getFunction();
+ $frame = [
+ 'class' => $function->class ?? null,
+ 'type' => isset($function->class) ? ($function->isStatic() ? '::' : '->') : null,
+ 'function' => $function->name,
+ 'file' => $c->getExecutingFile(),
+ 'line' => $c->getExecutingLine(),
+ ];
+ if ($trace = $c->getTrace(\DEBUG_BACKTRACE_IGNORE_ARGS)) {
+ $function = new \ReflectionGenerator($c->getExecutingGenerator());
+ array_unshift($trace, [
+ 'function' => 'yield',
+ 'file' => $function->getExecutingFile(),
+ 'line' => $function->getExecutingLine() - 1,
+ ]);
+ $trace[] = $frame;
+ $a[$prefix.'trace'] = new TraceStub($trace, false, 0, -1, -1);
+ } else {
+ $function = new FrameStub($frame, false, true);
+ $function = ExceptionCaster::castFrameStub($function, [], $function, true);
+ $a[$prefix.'executing'] = $function[$prefix.'src'];
+ }
+
+ $a[Caster::PREFIX_VIRTUAL.'closed'] = false;
+
+ return $a;
+ }
+
+ public static function castClass(\ReflectionClass $c, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ if ($n = \Reflection::getModifierNames($c->getModifiers())) {
+ $a[$prefix.'modifiers'] = implode(' ', $n);
+ }
+
+ self::addMap($a, $c, [
+ 'extends' => 'getParentClass',
+ 'implements' => 'getInterfaceNames',
+ 'constants' => 'getConstants',
+ ]);
+
+ foreach ($c->getProperties() as $n) {
+ $a[$prefix.'properties'][$n->name] = $n;
+ }
+
+ foreach ($c->getMethods() as $n) {
+ $a[$prefix.'methods'][$n->name] = $n;
+ }
+
+ if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) {
+ self::addExtra($a, $c);
+ }
+
+ return $a;
+ }
+
+ public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ self::addMap($a, $c, [
+ 'returnsReference' => 'returnsReference',
+ 'returnType' => 'getReturnType',
+ 'class' => 'getClosureScopeClass',
+ 'this' => 'getClosureThis',
+ ]);
+
+ if (isset($a[$prefix.'returnType'])) {
+ $v = $a[$prefix.'returnType'];
+ $v = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v;
+ $a[$prefix.'returnType'] = new ClassStub($a[$prefix.'returnType'] instanceof \ReflectionNamedType && $a[$prefix.'returnType']->allowsNull() && 'mixed' !== $v ? '?'.$v : $v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']);
+ }
+ if (isset($a[$prefix.'class'])) {
+ $a[$prefix.'class'] = new ClassStub($a[$prefix.'class']);
+ }
+ if (isset($a[$prefix.'this'])) {
+ $a[$prefix.'this'] = new CutStub($a[$prefix.'this']);
+ }
+
+ foreach ($c->getParameters() as $v) {
+ $k = '$'.$v->name;
+ if ($v->isVariadic()) {
+ $k = '...'.$k;
+ }
+ if ($v->isPassedByReference()) {
+ $k = '&'.$k;
+ }
+ $a[$prefix.'parameters'][$k] = $v;
+ }
+ if (isset($a[$prefix.'parameters'])) {
+ $a[$prefix.'parameters'] = new EnumStub($a[$prefix.'parameters']);
+ }
+
+ if (!($filter & Caster::EXCLUDE_VERBOSE) && $v = $c->getStaticVariables()) {
+ foreach ($v as $k => &$v) {
+ if (\is_object($v)) {
+ $a[$prefix.'use']['$'.$k] = new CutStub($v);
+ } else {
+ $a[$prefix.'use']['$'.$k] = &$v;
+ }
+ }
+ unset($v);
+ $a[$prefix.'use'] = new EnumStub($a[$prefix.'use']);
+ }
+
+ if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) {
+ self::addExtra($a, $c);
+ }
+
+ return $a;
+ }
+
+ public static function castMethod(\ReflectionMethod $c, array $a, Stub $stub, $isNested)
+ {
+ $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers()));
+
+ return $a;
+ }
+
+ public static function castParameter(\ReflectionParameter $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ self::addMap($a, $c, [
+ 'position' => 'getPosition',
+ 'isVariadic' => 'isVariadic',
+ 'byReference' => 'isPassedByReference',
+ 'allowsNull' => 'allowsNull',
+ ]);
+
+ if ($v = $c->getType()) {
+ $a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v;
+ }
+
+ if (isset($a[$prefix.'typeHint'])) {
+ $v = $a[$prefix.'typeHint'];
+ $a[$prefix.'typeHint'] = new ClassStub($v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']);
+ } else {
+ unset($a[$prefix.'allowsNull']);
+ }
+
+ try {
+ $a[$prefix.'default'] = $v = $c->getDefaultValue();
+ if ($c->isDefaultValueConstant()) {
+ $a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v);
+ }
+ if (null === $v) {
+ unset($a[$prefix.'allowsNull']);
+ }
+ } catch (\ReflectionException $e) {
+ }
+
+ return $a;
+ }
+
+ public static function castProperty(\ReflectionProperty $c, array $a, Stub $stub, $isNested)
+ {
+ $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers()));
+ self::addExtra($a, $c);
+
+ return $a;
+ }
+
+ public static function castReference(\ReflectionReference $c, array $a, Stub $stub, $isNested)
+ {
+ $a[Caster::PREFIX_VIRTUAL.'id'] = $c->getId();
+
+ return $a;
+ }
+
+ public static function castExtension(\ReflectionExtension $c, array $a, Stub $stub, $isNested)
+ {
+ self::addMap($a, $c, [
+ 'version' => 'getVersion',
+ 'dependencies' => 'getDependencies',
+ 'iniEntries' => 'getIniEntries',
+ 'isPersistent' => 'isPersistent',
+ 'isTemporary' => 'isTemporary',
+ 'constants' => 'getConstants',
+ 'functions' => 'getFunctions',
+ 'classes' => 'getClasses',
+ ]);
+
+ return $a;
+ }
+
+ public static function castZendExtension(\ReflectionZendExtension $c, array $a, Stub $stub, $isNested)
+ {
+ self::addMap($a, $c, [
+ 'version' => 'getVersion',
+ 'author' => 'getAuthor',
+ 'copyright' => 'getCopyright',
+ 'url' => 'getURL',
+ ]);
+
+ return $a;
+ }
+
+ public static function getSignature(array $a)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $signature = '';
+
+ if (isset($a[$prefix.'parameters'])) {
+ foreach ($a[$prefix.'parameters']->value as $k => $param) {
+ $signature .= ', ';
+ if ($type = $param->getType()) {
+ if (!$type instanceof \ReflectionNamedType) {
+ $signature .= $type.' ';
+ } else {
+ if (!$param->isOptional() && $param->allowsNull() && 'mixed' !== $type->getName()) {
+ $signature .= '?';
+ }
+ $signature .= substr(strrchr('\\'.$type->getName(), '\\'), 1).' ';
+ }
+ }
+ $signature .= $k;
+
+ if (!$param->isDefaultValueAvailable()) {
+ continue;
+ }
+ $v = $param->getDefaultValue();
+ $signature .= ' = ';
+
+ if ($param->isDefaultValueConstant()) {
+ $signature .= substr(strrchr('\\'.$param->getDefaultValueConstantName(), '\\'), 1);
+ } elseif (null === $v) {
+ $signature .= 'null';
+ } elseif (\is_array($v)) {
+ $signature .= $v ? '[…'.\count($v).']' : '[]';
+ } elseif (\is_string($v)) {
+ $signature .= 10 > \strlen($v) && false === strpos($v, '\\') ? "'{$v}'" : "'…".\strlen($v)."'";
+ } elseif (\is_bool($v)) {
+ $signature .= $v ? 'true' : 'false';
+ } else {
+ $signature .= $v;
+ }
+ }
+ }
+ $signature = (empty($a[$prefix.'returnsReference']) ? '' : '&').'('.substr($signature, 2).')';
+
+ if (isset($a[$prefix.'returnType'])) {
+ $signature .= ': '.substr(strrchr('\\'.$a[$prefix.'returnType'], '\\'), 1);
+ }
+
+ return $signature;
+ }
+
+ private static function addExtra(array &$a, \Reflector $c)
+ {
+ $x = isset($a[Caster::PREFIX_VIRTUAL.'extra']) ? $a[Caster::PREFIX_VIRTUAL.'extra']->value : [];
+
+ if (method_exists($c, 'getFileName') && $m = $c->getFileName()) {
+ $x['file'] = new LinkStub($m, $c->getStartLine());
+ $x['line'] = $c->getStartLine().' to '.$c->getEndLine();
+ }
+
+ self::addMap($x, $c, self::EXTRA_MAP, '');
+
+ if ($x) {
+ $a[Caster::PREFIX_VIRTUAL.'extra'] = new EnumStub($x);
+ }
+ }
+
+ private static function addMap(array &$a, $c, array $map, string $prefix = Caster::PREFIX_VIRTUAL)
+ {
+ foreach ($map as $k => $m) {
+ if (\PHP_VERSION_ID >= 80000 && 'isDisabled' === $k) {
+ continue;
+ }
+
+ if (method_exists($c, $m) && false !== ($m = $c->$m()) && null !== $m) {
+ $a[$prefix.$k] = $m instanceof \Reflector ? $m->name : $m;
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ResourceCaster.php b/vendor/symfony/var-dumper/Caster/ResourceCaster.php
new file mode 100644
index 000000000..5a7c42852
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ResourceCaster.php
@@ -0,0 +1,105 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts common resource types to array representation.
+ *
+ * @author Nicolas Grekas
+ *
+ * @final since Symfony 4.4
+ */
+class ResourceCaster
+{
+ /**
+ * @param \CurlHandle|resource $h
+ *
+ * @return array
+ */
+ public static function castCurl($h, array $a, Stub $stub, $isNested)
+ {
+ return curl_getinfo($h);
+ }
+
+ public static function castDba($dba, array $a, Stub $stub, $isNested)
+ {
+ $list = dba_list();
+ $a['file'] = $list[(int) $dba];
+
+ return $a;
+ }
+
+ public static function castProcess($process, array $a, Stub $stub, $isNested)
+ {
+ return proc_get_status($process);
+ }
+
+ public static function castStream($stream, array $a, Stub $stub, $isNested)
+ {
+ $a = stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested);
+ if (isset($a['uri'])) {
+ $a['uri'] = new LinkStub($a['uri']);
+ }
+
+ return $a;
+ }
+
+ public static function castStreamContext($stream, array $a, Stub $stub, $isNested)
+ {
+ return @stream_context_get_params($stream) ?: $a;
+ }
+
+ public static function castGd($gd, array $a, Stub $stub, $isNested)
+ {
+ $a['size'] = imagesx($gd).'x'.imagesy($gd);
+ $a['trueColor'] = imageistruecolor($gd);
+
+ return $a;
+ }
+
+ public static function castMysqlLink($h, array $a, Stub $stub, $isNested)
+ {
+ $a['host'] = mysql_get_host_info($h);
+ $a['protocol'] = mysql_get_proto_info($h);
+ $a['server'] = mysql_get_server_info($h);
+
+ return $a;
+ }
+
+ public static function castOpensslX509($h, array $a, Stub $stub, $isNested)
+ {
+ $stub->cut = -1;
+ $info = openssl_x509_parse($h, false);
+
+ $pin = openssl_pkey_get_public($h);
+ $pin = openssl_pkey_get_details($pin)['key'];
+ $pin = \array_slice(explode("\n", $pin), 1, -2);
+ $pin = base64_decode(implode('', $pin));
+ $pin = base64_encode(hash('sha256', $pin, true));
+
+ $a += [
+ 'subject' => new EnumStub(array_intersect_key($info['subject'], ['organizationName' => true, 'commonName' => true])),
+ 'issuer' => new EnumStub(array_intersect_key($info['issuer'], ['organizationName' => true, 'commonName' => true])),
+ 'expiry' => new ConstStub(date(\DateTime::ISO8601, $info['validTo_time_t']), $info['validTo_time_t']),
+ 'fingerprint' => new EnumStub([
+ 'md5' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'md5')), 2, ':', true)),
+ 'sha1' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha1')), 2, ':', true)),
+ 'sha256' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha256')), 2, ':', true)),
+ 'pin-sha256' => new ConstStub($pin),
+ ]),
+ ];
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/SplCaster.php b/vendor/symfony/var-dumper/Caster/SplCaster.php
new file mode 100644
index 000000000..5abc51a9f
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/SplCaster.php
@@ -0,0 +1,245 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts SPL related classes to array representation.
+ *
+ * @author Nicolas Grekas
+ *
+ * @final since Symfony 4.4
+ */
+class SplCaster
+{
+ private const SPL_FILE_OBJECT_FLAGS = [
+ \SplFileObject::DROP_NEW_LINE => 'DROP_NEW_LINE',
+ \SplFileObject::READ_AHEAD => 'READ_AHEAD',
+ \SplFileObject::SKIP_EMPTY => 'SKIP_EMPTY',
+ \SplFileObject::READ_CSV => 'READ_CSV',
+ ];
+
+ public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, $isNested)
+ {
+ return self::castSplArray($c, $a, $stub, $isNested);
+ }
+
+ public static function castArrayIterator(\ArrayIterator $c, array $a, Stub $stub, $isNested)
+ {
+ return self::castSplArray($c, $a, $stub, $isNested);
+ }
+
+ public static function castHeap(\Iterator $c, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'heap' => iterator_to_array(clone $c),
+ ];
+
+ return $a;
+ }
+
+ public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $mode = $c->getIteratorMode();
+ $c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE);
+
+ $a += [
+ $prefix.'mode' => new ConstStub((($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_DELETE) ? 'IT_MODE_DELETE' : 'IT_MODE_KEEP'), $mode),
+ $prefix.'dllist' => iterator_to_array($c),
+ ];
+ $c->setIteratorMode($mode);
+
+ return $a;
+ }
+
+ public static function castFileInfo(\SplFileInfo $c, array $a, Stub $stub, $isNested)
+ {
+ static $map = [
+ 'path' => 'getPath',
+ 'filename' => 'getFilename',
+ 'basename' => 'getBasename',
+ 'pathname' => 'getPathname',
+ 'extension' => 'getExtension',
+ 'realPath' => 'getRealPath',
+ 'aTime' => 'getATime',
+ 'mTime' => 'getMTime',
+ 'cTime' => 'getCTime',
+ 'inode' => 'getInode',
+ 'size' => 'getSize',
+ 'perms' => 'getPerms',
+ 'owner' => 'getOwner',
+ 'group' => 'getGroup',
+ 'type' => 'getType',
+ 'writable' => 'isWritable',
+ 'readable' => 'isReadable',
+ 'executable' => 'isExecutable',
+ 'file' => 'isFile',
+ 'dir' => 'isDir',
+ 'link' => 'isLink',
+ 'linkTarget' => 'getLinkTarget',
+ ];
+
+ $prefix = Caster::PREFIX_VIRTUAL;
+ unset($a["\0SplFileInfo\0fileName"]);
+ unset($a["\0SplFileInfo\0pathName"]);
+
+ if (\PHP_VERSION_ID < 80000) {
+ if (false === $c->getPathname()) {
+ $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state';
+
+ return $a;
+ }
+ } else {
+ try {
+ $c->isReadable();
+ } catch (\RuntimeException $e) {
+ if ('Object not initialized' !== $e->getMessage()) {
+ throw $e;
+ }
+
+ $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state';
+
+ return $a;
+ } catch (\Error $e) {
+ if ('Object not initialized' !== $e->getMessage()) {
+ throw $e;
+ }
+
+ $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state';
+
+ return $a;
+ }
+ }
+
+ foreach ($map as $key => $accessor) {
+ try {
+ $a[$prefix.$key] = $c->$accessor();
+ } catch (\Exception $e) {
+ }
+ }
+
+ if (isset($a[$prefix.'realPath'])) {
+ $a[$prefix.'realPath'] = new LinkStub($a[$prefix.'realPath']);
+ }
+
+ if (isset($a[$prefix.'perms'])) {
+ $a[$prefix.'perms'] = new ConstStub(sprintf('0%o', $a[$prefix.'perms']), $a[$prefix.'perms']);
+ }
+
+ static $mapDate = ['aTime', 'mTime', 'cTime'];
+ foreach ($mapDate as $key) {
+ if (isset($a[$prefix.$key])) {
+ $a[$prefix.$key] = new ConstStub(date('Y-m-d H:i:s', $a[$prefix.$key]), $a[$prefix.$key]);
+ }
+ }
+
+ return $a;
+ }
+
+ public static function castFileObject(\SplFileObject $c, array $a, Stub $stub, $isNested)
+ {
+ static $map = [
+ 'csvControl' => 'getCsvControl',
+ 'flags' => 'getFlags',
+ 'maxLineLen' => 'getMaxLineLen',
+ 'fstat' => 'fstat',
+ 'eof' => 'eof',
+ 'key' => 'key',
+ ];
+
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ foreach ($map as $key => $accessor) {
+ try {
+ $a[$prefix.$key] = $c->$accessor();
+ } catch (\Exception $e) {
+ }
+ }
+
+ if (isset($a[$prefix.'flags'])) {
+ $flagsArray = [];
+ foreach (self::SPL_FILE_OBJECT_FLAGS as $value => $name) {
+ if ($a[$prefix.'flags'] & $value) {
+ $flagsArray[] = $name;
+ }
+ }
+ $a[$prefix.'flags'] = new ConstStub(implode('|', $flagsArray), $a[$prefix.'flags']);
+ }
+
+ if (isset($a[$prefix.'fstat'])) {
+ $a[$prefix.'fstat'] = new CutArrayStub($a[$prefix.'fstat'], ['dev', 'ino', 'nlink', 'rdev', 'blksize', 'blocks']);
+ }
+
+ return $a;
+ }
+
+ public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $stub, $isNested)
+ {
+ $storage = [];
+ unset($a[Caster::PREFIX_DYNAMIC."\0gcdata"]); // Don't hit https://bugs.php.net/65967
+ unset($a["\0SplObjectStorage\0storage"]);
+
+ $clone = clone $c;
+ foreach ($clone as $obj) {
+ $storage[] = [
+ 'object' => $obj,
+ 'info' => $clone->getInfo(),
+ ];
+ }
+
+ $a += [
+ Caster::PREFIX_VIRTUAL.'storage' => $storage,
+ ];
+
+ return $a;
+ }
+
+ public static function castOuterIterator(\OuterIterator $c, array $a, Stub $stub, $isNested)
+ {
+ $a[Caster::PREFIX_VIRTUAL.'innerIterator'] = $c->getInnerIterator();
+
+ return $a;
+ }
+
+ public static function castWeakReference(\WeakReference $c, array $a, Stub $stub, $isNested)
+ {
+ $a[Caster::PREFIX_VIRTUAL.'object'] = $c->get();
+
+ return $a;
+ }
+
+ private static function castSplArray($c, array $a, Stub $stub, bool $isNested): array
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $flags = $c->getFlags();
+
+ if (!($flags & \ArrayObject::STD_PROP_LIST)) {
+ $c->setFlags(\ArrayObject::STD_PROP_LIST);
+ $a = Caster::castObject($c, \get_class($c), method_exists($c, '__debugInfo'), $stub->class);
+ $c->setFlags($flags);
+ }
+ if (\PHP_VERSION_ID < 70400) {
+ $a[$prefix.'storage'] = $c->getArrayCopy();
+ }
+ $a += [
+ $prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST),
+ $prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS),
+ ];
+ if ($c instanceof \ArrayObject) {
+ $a[$prefix.'iteratorClass'] = new ClassStub($c->getIteratorClass());
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/StubCaster.php b/vendor/symfony/var-dumper/Caster/StubCaster.php
new file mode 100644
index 000000000..b6332fb74
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/StubCaster.php
@@ -0,0 +1,84 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts a caster's Stub.
+ *
+ * @author Nicolas Grekas
+ *
+ * @final since Symfony 4.4
+ */
+class StubCaster
+{
+ public static function castStub(Stub $c, array $a, Stub $stub, $isNested)
+ {
+ if ($isNested) {
+ $stub->type = $c->type;
+ $stub->class = $c->class;
+ $stub->value = $c->value;
+ $stub->handle = $c->handle;
+ $stub->cut = $c->cut;
+ $stub->attr = $c->attr;
+
+ if (Stub::TYPE_REF === $c->type && !$c->class && \is_string($c->value) && !preg_match('//u', $c->value)) {
+ $stub->type = Stub::TYPE_STRING;
+ $stub->class = Stub::STRING_BINARY;
+ }
+
+ $a = [];
+ }
+
+ return $a;
+ }
+
+ public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, $isNested)
+ {
+ return $isNested ? $c->preservedSubset : $a;
+ }
+
+ public static function cutInternals($obj, array $a, Stub $stub, $isNested)
+ {
+ if ($isNested) {
+ $stub->cut += \count($a);
+
+ return [];
+ }
+
+ return $a;
+ }
+
+ public static function castEnum(EnumStub $c, array $a, Stub $stub, $isNested)
+ {
+ if ($isNested) {
+ $stub->class = $c->dumpKeys ? '' : null;
+ $stub->handle = 0;
+ $stub->value = null;
+ $stub->cut = $c->cut;
+ $stub->attr = $c->attr;
+
+ $a = [];
+
+ if ($c->value) {
+ foreach (array_keys($c->value) as $k) {
+ $keys[] = !isset($k[0]) || "\0" !== $k[0] ? Caster::PREFIX_VIRTUAL.$k : $k;
+ }
+ // Preserve references with array_combine()
+ $a = array_combine($keys, $c->value);
+ }
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/SymfonyCaster.php b/vendor/symfony/var-dumper/Caster/SymfonyCaster.php
new file mode 100644
index 000000000..06f213ef0
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/SymfonyCaster.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * @final since Symfony 4.4
+ */
+class SymfonyCaster
+{
+ private const REQUEST_GETTERS = [
+ 'pathInfo' => 'getPathInfo',
+ 'requestUri' => 'getRequestUri',
+ 'baseUrl' => 'getBaseUrl',
+ 'basePath' => 'getBasePath',
+ 'method' => 'getMethod',
+ 'format' => 'getRequestFormat',
+ ];
+
+ public static function castRequest(Request $request, array $a, Stub $stub, $isNested)
+ {
+ $clone = null;
+
+ foreach (self::REQUEST_GETTERS as $prop => $getter) {
+ $key = Caster::PREFIX_PROTECTED.$prop;
+ if (\array_key_exists($key, $a) && null === $a[$key]) {
+ if (null === $clone) {
+ $clone = clone $request;
+ }
+ $a[Caster::PREFIX_VIRTUAL.$prop] = $clone->{$getter}();
+ }
+ }
+
+ return $a;
+ }
+
+ public static function castHttpClient($client, array $a, Stub $stub, $isNested)
+ {
+ $multiKey = sprintf("\0%s\0multi", \get_class($client));
+ if (isset($a[$multiKey])) {
+ $a[$multiKey] = new CutStub($a[$multiKey]);
+ }
+
+ return $a;
+ }
+
+ public static function castHttpClientResponse($response, array $a, Stub $stub, $isNested)
+ {
+ $stub->cut += \count($a);
+ $a = [];
+
+ foreach ($response->getInfo() as $k => $v) {
+ $a[Caster::PREFIX_VIRTUAL.$k] = $v;
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/TraceStub.php b/vendor/symfony/var-dumper/Caster/TraceStub.php
new file mode 100644
index 000000000..5eea1c876
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/TraceStub.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Represents a backtrace as returned by debug_backtrace() or Exception->getTrace().
+ *
+ * @author Nicolas Grekas
+ */
+class TraceStub extends Stub
+{
+ public $keepArgs;
+ public $sliceOffset;
+ public $sliceLength;
+ public $numberingOffset;
+
+ public function __construct(array $trace, bool $keepArgs = true, int $sliceOffset = 0, int $sliceLength = null, int $numberingOffset = 0)
+ {
+ $this->value = $trace;
+ $this->keepArgs = $keepArgs;
+ $this->sliceOffset = $sliceOffset;
+ $this->sliceLength = $sliceLength;
+ $this->numberingOffset = $numberingOffset;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/UuidCaster.php b/vendor/symfony/var-dumper/Caster/UuidCaster.php
new file mode 100644
index 000000000..b10277457
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/UuidCaster.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Ramsey\Uuid\UuidInterface;
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * @author Grégoire Pineau
+ */
+final class UuidCaster
+{
+ public static function castRamseyUuid(UuidInterface $c, array $a, Stub $stub, bool $isNested): array
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'uuid' => (string) $c,
+ ];
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php
new file mode 100644
index 000000000..19bf6a3d5
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php
@@ -0,0 +1,79 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts XmlReader class to array representation.
+ *
+ * @author Baptiste Clavié
+ *
+ * @final since Symfony 4.4
+ */
+class XmlReaderCaster
+{
+ private const NODE_TYPES = [
+ \XMLReader::NONE => 'NONE',
+ \XMLReader::ELEMENT => 'ELEMENT',
+ \XMLReader::ATTRIBUTE => 'ATTRIBUTE',
+ \XMLReader::TEXT => 'TEXT',
+ \XMLReader::CDATA => 'CDATA',
+ \XMLReader::ENTITY_REF => 'ENTITY_REF',
+ \XMLReader::ENTITY => 'ENTITY',
+ \XMLReader::PI => 'PI (Processing Instruction)',
+ \XMLReader::COMMENT => 'COMMENT',
+ \XMLReader::DOC => 'DOC',
+ \XMLReader::DOC_TYPE => 'DOC_TYPE',
+ \XMLReader::DOC_FRAGMENT => 'DOC_FRAGMENT',
+ \XMLReader::NOTATION => 'NOTATION',
+ \XMLReader::WHITESPACE => 'WHITESPACE',
+ \XMLReader::SIGNIFICANT_WHITESPACE => 'SIGNIFICANT_WHITESPACE',
+ \XMLReader::END_ELEMENT => 'END_ELEMENT',
+ \XMLReader::END_ENTITY => 'END_ENTITY',
+ \XMLReader::XML_DECLARATION => 'XML_DECLARATION',
+ ];
+
+ public static function castXmlReader(\XMLReader $reader, array $a, Stub $stub, $isNested)
+ {
+ $props = Caster::PREFIX_VIRTUAL.'parserProperties';
+ $info = [
+ 'localName' => $reader->localName,
+ 'prefix' => $reader->prefix,
+ 'nodeType' => new ConstStub(self::NODE_TYPES[$reader->nodeType], $reader->nodeType),
+ 'depth' => $reader->depth,
+ 'isDefault' => $reader->isDefault,
+ 'isEmptyElement' => \XMLReader::NONE === $reader->nodeType ? null : $reader->isEmptyElement,
+ 'xmlLang' => $reader->xmlLang,
+ 'attributeCount' => $reader->attributeCount,
+ 'value' => $reader->value,
+ 'namespaceURI' => $reader->namespaceURI,
+ 'baseURI' => $reader->baseURI ? new LinkStub($reader->baseURI) : $reader->baseURI,
+ $props => [
+ 'LOADDTD' => $reader->getParserProperty(\XMLReader::LOADDTD),
+ 'DEFAULTATTRS' => $reader->getParserProperty(\XMLReader::DEFAULTATTRS),
+ 'VALIDATE' => $reader->getParserProperty(\XMLReader::VALIDATE),
+ 'SUBST_ENTITIES' => $reader->getParserProperty(\XMLReader::SUBST_ENTITIES),
+ ],
+ ];
+
+ if ($info[$props] = Caster::filter($info[$props], Caster::EXCLUDE_EMPTY, [], $count)) {
+ $info[$props] = new EnumStub($info[$props]);
+ $info[$props]->cut = $count;
+ }
+
+ $info = Caster::filter($info, Caster::EXCLUDE_EMPTY, [], $count);
+ // +2 because hasValue and hasAttributes are always filtered
+ $stub->cut += $count + 2;
+
+ return $a + $info;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php b/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php
new file mode 100644
index 000000000..455fc065b
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts XML resources to array representation.
+ *
+ * @author Nicolas Grekas
+ *
+ * @final since Symfony 4.4
+ */
+class XmlResourceCaster
+{
+ private const XML_ERRORS = [
+ \XML_ERROR_NONE => 'XML_ERROR_NONE',
+ \XML_ERROR_NO_MEMORY => 'XML_ERROR_NO_MEMORY',
+ \XML_ERROR_SYNTAX => 'XML_ERROR_SYNTAX',
+ \XML_ERROR_NO_ELEMENTS => 'XML_ERROR_NO_ELEMENTS',
+ \XML_ERROR_INVALID_TOKEN => 'XML_ERROR_INVALID_TOKEN',
+ \XML_ERROR_UNCLOSED_TOKEN => 'XML_ERROR_UNCLOSED_TOKEN',
+ \XML_ERROR_PARTIAL_CHAR => 'XML_ERROR_PARTIAL_CHAR',
+ \XML_ERROR_TAG_MISMATCH => 'XML_ERROR_TAG_MISMATCH',
+ \XML_ERROR_DUPLICATE_ATTRIBUTE => 'XML_ERROR_DUPLICATE_ATTRIBUTE',
+ \XML_ERROR_JUNK_AFTER_DOC_ELEMENT => 'XML_ERROR_JUNK_AFTER_DOC_ELEMENT',
+ \XML_ERROR_PARAM_ENTITY_REF => 'XML_ERROR_PARAM_ENTITY_REF',
+ \XML_ERROR_UNDEFINED_ENTITY => 'XML_ERROR_UNDEFINED_ENTITY',
+ \XML_ERROR_RECURSIVE_ENTITY_REF => 'XML_ERROR_RECURSIVE_ENTITY_REF',
+ \XML_ERROR_ASYNC_ENTITY => 'XML_ERROR_ASYNC_ENTITY',
+ \XML_ERROR_BAD_CHAR_REF => 'XML_ERROR_BAD_CHAR_REF',
+ \XML_ERROR_BINARY_ENTITY_REF => 'XML_ERROR_BINARY_ENTITY_REF',
+ \XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF => 'XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF',
+ \XML_ERROR_MISPLACED_XML_PI => 'XML_ERROR_MISPLACED_XML_PI',
+ \XML_ERROR_UNKNOWN_ENCODING => 'XML_ERROR_UNKNOWN_ENCODING',
+ \XML_ERROR_INCORRECT_ENCODING => 'XML_ERROR_INCORRECT_ENCODING',
+ \XML_ERROR_UNCLOSED_CDATA_SECTION => 'XML_ERROR_UNCLOSED_CDATA_SECTION',
+ \XML_ERROR_EXTERNAL_ENTITY_HANDLING => 'XML_ERROR_EXTERNAL_ENTITY_HANDLING',
+ ];
+
+ public static function castXml($h, array $a, Stub $stub, $isNested)
+ {
+ $a['current_byte_index'] = xml_get_current_byte_index($h);
+ $a['current_column_number'] = xml_get_current_column_number($h);
+ $a['current_line_number'] = xml_get_current_line_number($h);
+ $a['error_code'] = xml_get_error_code($h);
+
+ if (isset(self::XML_ERRORS[$a['error_code']])) {
+ $a['error_code'] = new ConstStub(self::XML_ERRORS[$a['error_code']], $a['error_code']);
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Cloner/AbstractCloner.php b/vendor/symfony/var-dumper/Cloner/AbstractCloner.php
new file mode 100644
index 000000000..178237905
--- /dev/null
+++ b/vendor/symfony/var-dumper/Cloner/AbstractCloner.php
@@ -0,0 +1,375 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Cloner;
+
+use Symfony\Component\VarDumper\Caster\Caster;
+use Symfony\Component\VarDumper\Exception\ThrowingCasterException;
+
+/**
+ * AbstractCloner implements a generic caster mechanism for objects and resources.
+ *
+ * @author Nicolas Grekas
+ */
+abstract class AbstractCloner implements ClonerInterface
+{
+ public static $defaultCasters = [
+ '__PHP_Incomplete_Class' => ['Symfony\Component\VarDumper\Caster\Caster', 'castPhpIncompleteClass'],
+
+ 'Symfony\Component\VarDumper\Caster\CutStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'],
+ 'Symfony\Component\VarDumper\Caster\CutArrayStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castCutArray'],
+ 'Symfony\Component\VarDumper\Caster\ConstStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'],
+ 'Symfony\Component\VarDumper\Caster\EnumStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castEnum'],
+
+ 'Closure' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClosure'],
+ 'Generator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castGenerator'],
+ 'ReflectionType' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castType'],
+ 'ReflectionGenerator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReflectionGenerator'],
+ 'ReflectionClass' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClass'],
+ 'ReflectionFunctionAbstract' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castFunctionAbstract'],
+ 'ReflectionMethod' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castMethod'],
+ 'ReflectionParameter' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castParameter'],
+ 'ReflectionProperty' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castProperty'],
+ 'ReflectionReference' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReference'],
+ 'ReflectionExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castExtension'],
+ 'ReflectionZendExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castZendExtension'],
+
+ 'Doctrine\Common\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+ 'Doctrine\Common\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castCommonProxy'],
+ 'Doctrine\ORM\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castOrmProxy'],
+ 'Doctrine\ORM\PersistentCollection' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castPersistentCollection'],
+ 'Doctrine\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+
+ 'DOMException' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'],
+ 'DOMStringList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'],
+ 'DOMNameList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'],
+ 'DOMImplementation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castImplementation'],
+ 'DOMImplementationList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'],
+ 'DOMNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNode'],
+ 'DOMNameSpaceNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNameSpaceNode'],
+ 'DOMDocument' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocument'],
+ 'DOMNodeList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'],
+ 'DOMNamedNodeMap' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'],
+ 'DOMCharacterData' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castCharacterData'],
+ 'DOMAttr' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castAttr'],
+ 'DOMElement' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castElement'],
+ 'DOMText' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castText'],
+ 'DOMTypeinfo' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castTypeinfo'],
+ 'DOMDomError' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDomError'],
+ 'DOMLocator' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLocator'],
+ 'DOMDocumentType' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocumentType'],
+ 'DOMNotation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNotation'],
+ 'DOMEntity' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castEntity'],
+ 'DOMProcessingInstruction' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'],
+ 'DOMXPath' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castXPath'],
+
+ 'XMLReader' => ['Symfony\Component\VarDumper\Caster\XmlReaderCaster', 'castXmlReader'],
+
+ 'ErrorException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castErrorException'],
+ 'Exception' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castException'],
+ 'Error' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castError'],
+ 'Symfony\Component\DependencyInjection\ContainerInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+ 'Symfony\Component\EventDispatcher\EventDispatcherInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+ 'Symfony\Component\HttpClient\CurlHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'],
+ 'Symfony\Component\HttpClient\NativeHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'],
+ 'Symfony\Component\HttpClient\Response\CurlResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'],
+ 'Symfony\Component\HttpClient\Response\NativeResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'],
+ 'Symfony\Component\HttpFoundation\Request' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castRequest'],
+ 'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castThrowingCasterException'],
+ 'Symfony\Component\VarDumper\Caster\TraceStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'],
+ 'Symfony\Component\VarDumper\Caster\FrameStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'],
+ 'Symfony\Component\VarDumper\Cloner\AbstractCloner' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+ 'Symfony\Component\ErrorHandler\Exception\SilencedErrorContext' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'],
+
+ 'Imagine\Image\ImageInterface' => ['Symfony\Component\VarDumper\Caster\ImagineCaster', 'castImage'],
+
+ 'Ramsey\Uuid\UuidInterface' => ['Symfony\Component\VarDumper\Caster\UuidCaster', 'castRamseyUuid'],
+
+ 'ProxyManager\Proxy\ProxyInterface' => ['Symfony\Component\VarDumper\Caster\ProxyManagerCaster', 'castProxy'],
+ 'PHPUnit_Framework_MockObject_MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+ 'PHPUnit\Framework\MockObject\MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+ 'PHPUnit\Framework\MockObject\Stub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+ 'Prophecy\Prophecy\ProphecySubjectInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+ 'Mockery\MockInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+
+ 'PDO' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdo'],
+ 'PDOStatement' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdoStatement'],
+
+ 'AMQPConnection' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castConnection'],
+ 'AMQPChannel' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castChannel'],
+ 'AMQPQueue' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castQueue'],
+ 'AMQPExchange' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castExchange'],
+ 'AMQPEnvelope' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castEnvelope'],
+
+ 'ArrayObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayObject'],
+ 'ArrayIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayIterator'],
+ 'SplDoublyLinkedList' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castDoublyLinkedList'],
+ 'SplFileInfo' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileInfo'],
+ 'SplFileObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileObject'],
+ 'SplHeap' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'],
+ 'SplObjectStorage' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castObjectStorage'],
+ 'SplPriorityQueue' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'],
+ 'OuterIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castOuterIterator'],
+ 'WeakReference' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakReference'],
+
+ 'Redis' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'],
+ 'RedisArray' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'],
+ 'RedisCluster' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisCluster'],
+
+ 'DateTimeInterface' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castDateTime'],
+ 'DateInterval' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castInterval'],
+ 'DateTimeZone' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castTimeZone'],
+ 'DatePeriod' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castPeriod'],
+
+ 'GMP' => ['Symfony\Component\VarDumper\Caster\GmpCaster', 'castGmp'],
+
+ 'MessageFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castMessageFormatter'],
+ 'NumberFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castNumberFormatter'],
+ 'IntlTimeZone' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlTimeZone'],
+ 'IntlCalendar' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlCalendar'],
+ 'IntlDateFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlDateFormatter'],
+
+ 'Memcached' => ['Symfony\Component\VarDumper\Caster\MemcachedCaster', 'castMemcached'],
+
+ 'Ds\Collection' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castCollection'],
+ 'Ds\Map' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castMap'],
+ 'Ds\Pair' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPair'],
+ 'Symfony\Component\VarDumper\Caster\DsPairStub' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPairStub'],
+
+ 'CurlHandle' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'],
+ ':curl' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'],
+
+ ':dba' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'],
+ ':dba persistent' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'],
+
+ 'GdImage' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'],
+ ':gd' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'],
+
+ ':mysql link' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castMysqlLink'],
+ ':pgsql large object' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'],
+ ':pgsql link' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'],
+ ':pgsql link persistent' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'],
+ ':pgsql result' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'],
+ ':process' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castProcess'],
+ ':stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'],
+
+ 'OpenSSLCertificate' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'],
+ ':OpenSSL X.509' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'],
+
+ ':persistent stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'],
+ ':stream-context' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStreamContext'],
+
+ 'XmlParser' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'],
+ ':xml' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'],
+ ];
+
+ protected $maxItems = 2500;
+ protected $maxString = -1;
+ protected $minDepth = 1;
+
+ private $casters = [];
+ private $prevErrorHandler;
+ private $classInfo = [];
+ private $filter = 0;
+
+ /**
+ * @param callable[]|null $casters A map of casters
+ *
+ * @see addCasters
+ */
+ public function __construct(array $casters = null)
+ {
+ if (null === $casters) {
+ $casters = static::$defaultCasters;
+ }
+ $this->addCasters($casters);
+ }
+
+ /**
+ * Adds casters for resources and objects.
+ *
+ * Maps resources or objects types to a callback.
+ * Types are in the key, with a callable caster for value.
+ * Resource types are to be prefixed with a `:`,
+ * see e.g. static::$defaultCasters.
+ *
+ * @param callable[] $casters A map of casters
+ */
+ public function addCasters(array $casters)
+ {
+ foreach ($casters as $type => $callback) {
+ $this->casters[$type][] = $callback;
+ }
+ }
+
+ /**
+ * Sets the maximum number of items to clone past the minimum depth in nested structures.
+ *
+ * @param int $maxItems
+ */
+ public function setMaxItems($maxItems)
+ {
+ $this->maxItems = (int) $maxItems;
+ }
+
+ /**
+ * Sets the maximum cloned length for strings.
+ *
+ * @param int $maxString
+ */
+ public function setMaxString($maxString)
+ {
+ $this->maxString = (int) $maxString;
+ }
+
+ /**
+ * Sets the minimum tree depth where we are guaranteed to clone all the items. After this
+ * depth is reached, only setMaxItems items will be cloned.
+ *
+ * @param int $minDepth
+ */
+ public function setMinDepth($minDepth)
+ {
+ $this->minDepth = (int) $minDepth;
+ }
+
+ /**
+ * Clones a PHP variable.
+ *
+ * @param mixed $var Any PHP variable
+ * @param int $filter A bit field of Caster::EXCLUDE_* constants
+ *
+ * @return Data The cloned variable represented by a Data object
+ */
+ public function cloneVar($var, $filter = 0)
+ {
+ $this->prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) {
+ if (\E_RECOVERABLE_ERROR === $type || \E_USER_ERROR === $type) {
+ // Cloner never dies
+ throw new \ErrorException($msg, 0, $type, $file, $line);
+ }
+
+ if ($this->prevErrorHandler) {
+ return ($this->prevErrorHandler)($type, $msg, $file, $line, $context);
+ }
+
+ return false;
+ });
+ $this->filter = $filter;
+
+ if ($gc = gc_enabled()) {
+ gc_disable();
+ }
+ try {
+ return new Data($this->doClone($var));
+ } finally {
+ if ($gc) {
+ gc_enable();
+ }
+ restore_error_handler();
+ $this->prevErrorHandler = null;
+ }
+ }
+
+ /**
+ * Effectively clones the PHP variable.
+ *
+ * @param mixed $var Any PHP variable
+ *
+ * @return array The cloned variable represented in an array
+ */
+ abstract protected function doClone($var);
+
+ /**
+ * Casts an object to an array representation.
+ *
+ * @param bool $isNested True if the object is nested in the dumped structure
+ *
+ * @return array The object casted as array
+ */
+ protected function castObject(Stub $stub, $isNested)
+ {
+ $obj = $stub->value;
+ $class = $stub->class;
+
+ if (\PHP_VERSION_ID < 80000 ? "\0" === ($class[15] ?? null) : false !== strpos($class, "@anonymous\0")) {
+ $stub->class = get_debug_type($obj);
+ }
+ if (isset($this->classInfo[$class])) {
+ [$i, $parents, $hasDebugInfo, $fileInfo] = $this->classInfo[$class];
+ } else {
+ $i = 2;
+ $parents = [$class];
+ $hasDebugInfo = method_exists($class, '__debugInfo');
+
+ foreach (class_parents($class) as $p) {
+ $parents[] = $p;
+ ++$i;
+ }
+ foreach (class_implements($class) as $p) {
+ $parents[] = $p;
+ ++$i;
+ }
+ $parents[] = '*';
+
+ $r = new \ReflectionClass($class);
+ $fileInfo = $r->isInternal() || $r->isSubclassOf(Stub::class) ? [] : [
+ 'file' => $r->getFileName(),
+ 'line' => $r->getStartLine(),
+ ];
+
+ $this->classInfo[$class] = [$i, $parents, $hasDebugInfo, $fileInfo];
+ }
+
+ $stub->attr += $fileInfo;
+ $a = Caster::castObject($obj, $class, $hasDebugInfo, $stub->class);
+
+ try {
+ while ($i--) {
+ if (!empty($this->casters[$p = $parents[$i]])) {
+ foreach ($this->casters[$p] as $callback) {
+ $a = $callback($obj, $a, $stub, $isNested, $this->filter);
+ }
+ }
+ }
+ } catch (\Exception $e) {
+ $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a;
+ }
+
+ return $a;
+ }
+
+ /**
+ * Casts a resource to an array representation.
+ *
+ * @param bool $isNested True if the object is nested in the dumped structure
+ *
+ * @return array The resource casted as array
+ */
+ protected function castResource(Stub $stub, $isNested)
+ {
+ $a = [];
+ $res = $stub->value;
+ $type = $stub->class;
+
+ try {
+ if (!empty($this->casters[':'.$type])) {
+ foreach ($this->casters[':'.$type] as $callback) {
+ $a = $callback($res, $a, $stub, $isNested, $this->filter);
+ }
+ }
+ } catch (\Exception $e) {
+ $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a;
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Cloner/ClonerInterface.php b/vendor/symfony/var-dumper/Cloner/ClonerInterface.php
new file mode 100644
index 000000000..7ed287a2d
--- /dev/null
+++ b/vendor/symfony/var-dumper/Cloner/ClonerInterface.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Cloner;
+
+/**
+ * @author Nicolas Grekas
+ */
+interface ClonerInterface
+{
+ /**
+ * Clones a PHP variable.
+ *
+ * @param mixed $var Any PHP variable
+ *
+ * @return Data The cloned variable represented by a Data object
+ */
+ public function cloneVar($var);
+}
diff --git a/vendor/symfony/var-dumper/Cloner/Cursor.php b/vendor/symfony/var-dumper/Cloner/Cursor.php
new file mode 100644
index 000000000..1fd796d67
--- /dev/null
+++ b/vendor/symfony/var-dumper/Cloner/Cursor.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Cloner;
+
+/**
+ * Represents the current state of a dumper while dumping.
+ *
+ * @author Nicolas Grekas
+ */
+class Cursor
+{
+ public const HASH_INDEXED = Stub::ARRAY_INDEXED;
+ public const HASH_ASSOC = Stub::ARRAY_ASSOC;
+ public const HASH_OBJECT = Stub::TYPE_OBJECT;
+ public const HASH_RESOURCE = Stub::TYPE_RESOURCE;
+
+ public $depth = 0;
+ public $refIndex = 0;
+ public $softRefTo = 0;
+ public $softRefCount = 0;
+ public $softRefHandle = 0;
+ public $hardRefTo = 0;
+ public $hardRefCount = 0;
+ public $hardRefHandle = 0;
+ public $hashType;
+ public $hashKey;
+ public $hashKeyIsBinary;
+ public $hashIndex = 0;
+ public $hashLength = 0;
+ public $hashCut = 0;
+ public $stop = false;
+ public $attr = [];
+ public $skipChildren = false;
+}
diff --git a/vendor/symfony/var-dumper/Cloner/Data.php b/vendor/symfony/var-dumper/Cloner/Data.php
new file mode 100644
index 000000000..21adb2364
--- /dev/null
+++ b/vendor/symfony/var-dumper/Cloner/Data.php
@@ -0,0 +1,455 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Cloner;
+
+use Symfony\Component\VarDumper\Caster\Caster;
+use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
+
+/**
+ * @author Nicolas Grekas
+ */
+class Data implements \ArrayAccess, \Countable, \IteratorAggregate
+{
+ private $data;
+ private $position = 0;
+ private $key = 0;
+ private $maxDepth = 20;
+ private $maxItemsPerDepth = -1;
+ private $useRefHandles = -1;
+ private $context = [];
+
+ /**
+ * @param array $data An array as returned by ClonerInterface::cloneVar()
+ */
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * @return string|null The type of the value
+ */
+ public function getType()
+ {
+ $item = $this->data[$this->position][$this->key];
+
+ if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
+ $item = $item->value;
+ }
+ if (!$item instanceof Stub) {
+ return \gettype($item);
+ }
+ if (Stub::TYPE_STRING === $item->type) {
+ return 'string';
+ }
+ if (Stub::TYPE_ARRAY === $item->type) {
+ return 'array';
+ }
+ if (Stub::TYPE_OBJECT === $item->type) {
+ return $item->class;
+ }
+ if (Stub::TYPE_RESOURCE === $item->type) {
+ return $item->class.' resource';
+ }
+
+ return null;
+ }
+
+ /**
+ * @param array|bool $recursive Whether values should be resolved recursively or not
+ *
+ * @return string|int|float|bool|array|Data[]|null A native representation of the original value
+ */
+ public function getValue($recursive = false)
+ {
+ $item = $this->data[$this->position][$this->key];
+
+ if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
+ $item = $item->value;
+ }
+ if (!($item = $this->getStub($item)) instanceof Stub) {
+ return $item;
+ }
+ if (Stub::TYPE_STRING === $item->type) {
+ return $item->value;
+ }
+
+ $children = $item->position ? $this->data[$item->position] : [];
+
+ foreach ($children as $k => $v) {
+ if ($recursive && !($v = $this->getStub($v)) instanceof Stub) {
+ continue;
+ }
+ $children[$k] = clone $this;
+ $children[$k]->key = $k;
+ $children[$k]->position = $item->position;
+
+ if ($recursive) {
+ if (Stub::TYPE_REF === $v->type && ($v = $this->getStub($v->value)) instanceof Stub) {
+ $recursive = (array) $recursive;
+ if (isset($recursive[$v->position])) {
+ continue;
+ }
+ $recursive[$v->position] = true;
+ }
+ $children[$k] = $children[$k]->getValue($recursive);
+ }
+ }
+
+ return $children;
+ }
+
+ /**
+ * @return int
+ */
+ public function count()
+ {
+ return \count($this->getValue());
+ }
+
+ /**
+ * @return \Traversable
+ */
+ public function getIterator()
+ {
+ if (!\is_array($value = $this->getValue())) {
+ throw new \LogicException(sprintf('"%s" object holds non-iterable type "%s".', self::class, \gettype($value)));
+ }
+
+ yield from $value;
+ }
+
+ public function __get($key)
+ {
+ if (null !== $data = $this->seek($key)) {
+ $item = $this->getStub($data->data[$data->position][$data->key]);
+
+ return $item instanceof Stub || [] === $item ? $data : $item;
+ }
+
+ return null;
+ }
+
+ /**
+ * @return bool
+ */
+ public function __isset($key)
+ {
+ return null !== $this->seek($key);
+ }
+
+ /**
+ * @return bool
+ */
+ public function offsetExists($key)
+ {
+ return $this->__isset($key);
+ }
+
+ public function offsetGet($key)
+ {
+ return $this->__get($key);
+ }
+
+ public function offsetSet($key, $value)
+ {
+ throw new \BadMethodCallException(self::class.' objects are immutable.');
+ }
+
+ public function offsetUnset($key)
+ {
+ throw new \BadMethodCallException(self::class.' objects are immutable.');
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ $value = $this->getValue();
+
+ if (!\is_array($value)) {
+ return (string) $value;
+ }
+
+ return sprintf('%s (count=%d)', $this->getType(), \count($value));
+ }
+
+ /**
+ * Returns a depth limited clone of $this.
+ *
+ * @param int $maxDepth The max dumped depth level
+ *
+ * @return static
+ */
+ public function withMaxDepth($maxDepth)
+ {
+ $data = clone $this;
+ $data->maxDepth = (int) $maxDepth;
+
+ return $data;
+ }
+
+ /**
+ * Limits the number of elements per depth level.
+ *
+ * @param int $maxItemsPerDepth The max number of items dumped per depth level
+ *
+ * @return static
+ */
+ public function withMaxItemsPerDepth($maxItemsPerDepth)
+ {
+ $data = clone $this;
+ $data->maxItemsPerDepth = (int) $maxItemsPerDepth;
+
+ return $data;
+ }
+
+ /**
+ * Enables/disables objects' identifiers tracking.
+ *
+ * @param bool $useRefHandles False to hide global ref. handles
+ *
+ * @return static
+ */
+ public function withRefHandles($useRefHandles)
+ {
+ $data = clone $this;
+ $data->useRefHandles = $useRefHandles ? -1 : 0;
+
+ return $data;
+ }
+
+ /**
+ * @return static
+ */
+ public function withContext(array $context)
+ {
+ $data = clone $this;
+ $data->context = $context;
+
+ return $data;
+ }
+
+ /**
+ * Seeks to a specific key in nested data structures.
+ *
+ * @param string|int $key The key to seek to
+ *
+ * @return static|null Null if the key is not set
+ */
+ public function seek($key)
+ {
+ $item = $this->data[$this->position][$this->key];
+
+ if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
+ $item = $item->value;
+ }
+ if (!($item = $this->getStub($item)) instanceof Stub || !$item->position) {
+ return null;
+ }
+ $keys = [$key];
+
+ switch ($item->type) {
+ case Stub::TYPE_OBJECT:
+ $keys[] = Caster::PREFIX_DYNAMIC.$key;
+ $keys[] = Caster::PREFIX_PROTECTED.$key;
+ $keys[] = Caster::PREFIX_VIRTUAL.$key;
+ $keys[] = "\0$item->class\0$key";
+ // no break
+ case Stub::TYPE_ARRAY:
+ case Stub::TYPE_RESOURCE:
+ break;
+ default:
+ return null;
+ }
+
+ $data = null;
+ $children = $this->data[$item->position];
+
+ foreach ($keys as $key) {
+ if (isset($children[$key]) || \array_key_exists($key, $children)) {
+ $data = clone $this;
+ $data->key = $key;
+ $data->position = $item->position;
+ break;
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Dumps data with a DumperInterface dumper.
+ */
+ public function dump(DumperInterface $dumper)
+ {
+ $refs = [0];
+ $cursor = new Cursor();
+
+ if ($cursor->attr = $this->context[SourceContextProvider::class] ?? []) {
+ $cursor->attr['if_links'] = true;
+ $cursor->hashType = -1;
+ $dumper->dumpScalar($cursor, 'default', '^');
+ $cursor->attr = ['if_links' => true];
+ $dumper->dumpScalar($cursor, 'default', ' ');
+ $cursor->hashType = 0;
+ }
+
+ $this->dumpItem($dumper, $cursor, $refs, $this->data[$this->position][$this->key]);
+ }
+
+ /**
+ * Depth-first dumping of items.
+ *
+ * @param mixed $item A Stub object or the original value being dumped
+ */
+ private function dumpItem(DumperInterface $dumper, Cursor $cursor, array &$refs, $item)
+ {
+ $cursor->refIndex = 0;
+ $cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0;
+ $cursor->hardRefTo = $cursor->hardRefHandle = $cursor->hardRefCount = 0;
+ $firstSeen = true;
+
+ if (!$item instanceof Stub) {
+ $cursor->attr = [];
+ $type = \gettype($item);
+ if ($item && 'array' === $type) {
+ $item = $this->getStub($item);
+ }
+ } elseif (Stub::TYPE_REF === $item->type) {
+ if ($item->handle) {
+ if (!isset($refs[$r = $item->handle - (\PHP_INT_MAX >> 1)])) {
+ $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0];
+ } else {
+ $firstSeen = false;
+ }
+ $cursor->hardRefTo = $refs[$r];
+ $cursor->hardRefHandle = $this->useRefHandles & $item->handle;
+ $cursor->hardRefCount = 0 < $item->handle ? $item->refCount : 0;
+ }
+ $cursor->attr = $item->attr;
+ $type = $item->class ?: \gettype($item->value);
+ $item = $this->getStub($item->value);
+ }
+ if ($item instanceof Stub) {
+ if ($item->refCount) {
+ if (!isset($refs[$r = $item->handle])) {
+ $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0];
+ } else {
+ $firstSeen = false;
+ }
+ $cursor->softRefTo = $refs[$r];
+ }
+ $cursor->softRefHandle = $this->useRefHandles & $item->handle;
+ $cursor->softRefCount = $item->refCount;
+ $cursor->attr = $item->attr;
+ $cut = $item->cut;
+
+ if ($item->position && $firstSeen) {
+ $children = $this->data[$item->position];
+
+ if ($cursor->stop) {
+ if ($cut >= 0) {
+ $cut += \count($children);
+ }
+ $children = [];
+ }
+ } else {
+ $children = [];
+ }
+ switch ($item->type) {
+ case Stub::TYPE_STRING:
+ $dumper->dumpString($cursor, $item->value, Stub::STRING_BINARY === $item->class, $cut);
+ break;
+
+ case Stub::TYPE_ARRAY:
+ $item = clone $item;
+ $item->type = $item->class;
+ $item->class = $item->value;
+ // no break
+ case Stub::TYPE_OBJECT:
+ case Stub::TYPE_RESOURCE:
+ $withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth;
+ $dumper->enterHash($cursor, $item->type, $item->class, $withChildren);
+ if ($withChildren) {
+ if ($cursor->skipChildren) {
+ $withChildren = false;
+ $cut = -1;
+ } else {
+ $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type, null !== $item->class);
+ }
+ } elseif ($children && 0 <= $cut) {
+ $cut += \count($children);
+ }
+ $cursor->skipChildren = false;
+ $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut);
+ break;
+
+ default:
+ throw new \RuntimeException(sprintf('Unexpected Stub type: "%s".', $item->type));
+ }
+ } elseif ('array' === $type) {
+ $dumper->enterHash($cursor, Cursor::HASH_INDEXED, 0, false);
+ $dumper->leaveHash($cursor, Cursor::HASH_INDEXED, 0, false, 0);
+ } elseif ('string' === $type) {
+ $dumper->dumpString($cursor, $item, false, 0);
+ } else {
+ $dumper->dumpScalar($cursor, $type, $item);
+ }
+ }
+
+ /**
+ * Dumps children of hash structures.
+ *
+ * @return int The final number of removed items
+ */
+ private function dumpChildren(DumperInterface $dumper, Cursor $parentCursor, array &$refs, array $children, int $hashCut, int $hashType, bool $dumpKeys): int
+ {
+ $cursor = clone $parentCursor;
+ ++$cursor->depth;
+ $cursor->hashType = $hashType;
+ $cursor->hashIndex = 0;
+ $cursor->hashLength = \count($children);
+ $cursor->hashCut = $hashCut;
+ foreach ($children as $key => $child) {
+ $cursor->hashKeyIsBinary = isset($key[0]) && !preg_match('//u', $key);
+ $cursor->hashKey = $dumpKeys ? $key : null;
+ $this->dumpItem($dumper, $cursor, $refs, $child);
+ if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) {
+ $parentCursor->stop = true;
+
+ return $hashCut >= 0 ? $hashCut + $cursor->hashLength - $cursor->hashIndex : $hashCut;
+ }
+ }
+
+ return $hashCut;
+ }
+
+ private function getStub($item)
+ {
+ if (!$item || !\is_array($item)) {
+ return $item;
+ }
+
+ $stub = new Stub();
+ $stub->type = Stub::TYPE_ARRAY;
+ foreach ($item as $stub->class => $stub->position) {
+ }
+ if (isset($item[0])) {
+ $stub->cut = $item[0];
+ }
+ $stub->value = $stub->cut + ($stub->position ? \count($this->data[$stub->position]) : 0);
+
+ return $stub;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Cloner/DumperInterface.php b/vendor/symfony/var-dumper/Cloner/DumperInterface.php
new file mode 100644
index 000000000..ec8ef2727
--- /dev/null
+++ b/vendor/symfony/var-dumper/Cloner/DumperInterface.php
@@ -0,0 +1,56 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Cloner;
+
+/**
+ * DumperInterface used by Data objects.
+ *
+ * @author Nicolas Grekas
+ */
+interface DumperInterface
+{
+ /**
+ * Dumps a scalar value.
+ *
+ * @param string $type The PHP type of the value being dumped
+ * @param string|int|float|bool $value The scalar value being dumped
+ */
+ public function dumpScalar(Cursor $cursor, $type, $value);
+
+ /**
+ * Dumps a string.
+ *
+ * @param string $str The string being dumped
+ * @param bool $bin Whether $str is UTF-8 or binary encoded
+ * @param int $cut The number of characters $str has been cut by
+ */
+ public function dumpString(Cursor $cursor, $str, $bin, $cut);
+
+ /**
+ * Dumps while entering an hash.
+ *
+ * @param int $type A Cursor::HASH_* const for the type of hash
+ * @param string|int $class The object class, resource type or array count
+ * @param bool $hasChild When the dump of the hash has child item
+ */
+ public function enterHash(Cursor $cursor, $type, $class, $hasChild);
+
+ /**
+ * Dumps while leaving an hash.
+ *
+ * @param int $type A Cursor::HASH_* const for the type of hash
+ * @param string|int $class The object class, resource type or array count
+ * @param bool $hasChild When the dump of the hash has child item
+ * @param int $cut The number of items the hash has been cut by
+ */
+ public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut);
+}
diff --git a/vendor/symfony/var-dumper/Cloner/Stub.php b/vendor/symfony/var-dumper/Cloner/Stub.php
new file mode 100644
index 000000000..073c56efb
--- /dev/null
+++ b/vendor/symfony/var-dumper/Cloner/Stub.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Cloner;
+
+/**
+ * Represents the main properties of a PHP variable.
+ *
+ * @author Nicolas Grekas
+ */
+class Stub
+{
+ public const TYPE_REF = 1;
+ public const TYPE_STRING = 2;
+ public const TYPE_ARRAY = 3;
+ public const TYPE_OBJECT = 4;
+ public const TYPE_RESOURCE = 5;
+
+ public const STRING_BINARY = 1;
+ public const STRING_UTF8 = 2;
+
+ public const ARRAY_ASSOC = 1;
+ public const ARRAY_INDEXED = 2;
+
+ public $type = self::TYPE_REF;
+ public $class = '';
+ public $value;
+ public $cut = 0;
+ public $handle = 0;
+ public $refCount = 0;
+ public $position = 0;
+ public $attr = [];
+
+ private static $defaultProperties = [];
+
+ /**
+ * @internal
+ */
+ public function __sleep(): array
+ {
+ $properties = [];
+
+ if (!isset(self::$defaultProperties[$c = static::class])) {
+ self::$defaultProperties[$c] = get_class_vars($c);
+
+ foreach ((new \ReflectionClass($c))->getStaticProperties() as $k => $v) {
+ unset(self::$defaultProperties[$c][$k]);
+ }
+ }
+
+ foreach (self::$defaultProperties[$c] as $k => $v) {
+ if ($this->$k !== $v) {
+ $properties[] = $k;
+ }
+ }
+
+ return $properties;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Cloner/VarCloner.php b/vendor/symfony/var-dumper/Cloner/VarCloner.php
new file mode 100644
index 000000000..6a9002137
--- /dev/null
+++ b/vendor/symfony/var-dumper/Cloner/VarCloner.php
@@ -0,0 +1,307 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Cloner;
+
+/**
+ * @author Nicolas Grekas
+ */
+class VarCloner extends AbstractCloner
+{
+ private static $gid;
+ private static $arrayCache = [];
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClone($var)
+ {
+ $len = 1; // Length of $queue
+ $pos = 0; // Number of cloned items past the minimum depth
+ $refsCounter = 0; // Hard references counter
+ $queue = [[$var]]; // This breadth-first queue is the return value
+ $indexedArrays = []; // Map of queue indexes that hold numerically indexed arrays
+ $hardRefs = []; // Map of original zval ids to stub objects
+ $objRefs = []; // Map of original object handles to their stub object counterpart
+ $objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning
+ $resRefs = []; // Map of original resource handles to their stub object counterpart
+ $values = []; // Map of stub objects' ids to original values
+ $maxItems = $this->maxItems;
+ $maxString = $this->maxString;
+ $minDepth = $this->minDepth;
+ $currentDepth = 0; // Current tree depth
+ $currentDepthFinalIndex = 0; // Final $queue index for current tree depth
+ $minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached
+ $cookie = (object) []; // Unique object used to detect hard references
+ $a = null; // Array cast for nested structures
+ $stub = null; // Stub capturing the main properties of an original item value
+ // or null if the original value is used directly
+
+ if (!$gid = self::$gid) {
+ $gid = self::$gid = md5(random_bytes(6)); // Unique string used to detect the special $GLOBALS variable
+ }
+ $arrayStub = new Stub();
+ $arrayStub->type = Stub::TYPE_ARRAY;
+ $fromObjCast = false;
+
+ for ($i = 0; $i < $len; ++$i) {
+ // Detect when we move on to the next tree depth
+ if ($i > $currentDepthFinalIndex) {
+ ++$currentDepth;
+ $currentDepthFinalIndex = $len - 1;
+ if ($currentDepth >= $minDepth) {
+ $minimumDepthReached = true;
+ }
+ }
+
+ $refs = $vals = $queue[$i];
+ if (\PHP_VERSION_ID < 70200 && empty($indexedArrays[$i])) {
+ // see https://wiki.php.net/rfc/convert_numeric_keys_in_object_array_casts
+ foreach ($vals as $k => $v) {
+ if (\is_int($k)) {
+ continue;
+ }
+ foreach ([$k => true] as $gk => $gv) {
+ }
+ if ($gk !== $k) {
+ $fromObjCast = true;
+ $refs = $vals = array_values($queue[$i]);
+ break;
+ }
+ }
+ }
+ foreach ($vals as $k => $v) {
+ // $v is the original value or a stub object in case of hard references
+
+ if (\PHP_VERSION_ID >= 70400) {
+ $zvalIsRef = null !== \ReflectionReference::fromArrayElement($vals, $k);
+ } else {
+ $refs[$k] = $cookie;
+ $zvalIsRef = $vals[$k] === $cookie;
+ }
+
+ if ($zvalIsRef) {
+ $vals[$k] = &$stub; // Break hard references to make $queue completely
+ unset($stub); // independent from the original structure
+ if ($v instanceof Stub && isset($hardRefs[spl_object_id($v)])) {
+ $vals[$k] = $refs[$k] = $v;
+ if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) {
+ ++$v->value->refCount;
+ }
+ ++$v->refCount;
+ continue;
+ }
+ $refs[$k] = $vals[$k] = new Stub();
+ $refs[$k]->value = $v;
+ $h = spl_object_id($refs[$k]);
+ $hardRefs[$h] = &$refs[$k];
+ $values[$h] = $v;
+ $vals[$k]->handle = ++$refsCounter;
+ }
+ // Create $stub when the original value $v can not be used directly
+ // If $v is a nested structure, put that structure in array $a
+ switch (true) {
+ case null === $v:
+ case \is_bool($v):
+ case \is_int($v):
+ case \is_float($v):
+ continue 2;
+ case \is_string($v):
+ if ('' === $v) {
+ continue 2;
+ }
+ if (!preg_match('//u', $v)) {
+ $stub = new Stub();
+ $stub->type = Stub::TYPE_STRING;
+ $stub->class = Stub::STRING_BINARY;
+ if (0 <= $maxString && 0 < $cut = \strlen($v) - $maxString) {
+ $stub->cut = $cut;
+ $stub->value = substr($v, 0, -$cut);
+ } else {
+ $stub->value = $v;
+ }
+ } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) {
+ $stub = new Stub();
+ $stub->type = Stub::TYPE_STRING;
+ $stub->class = Stub::STRING_UTF8;
+ $stub->cut = $cut;
+ $stub->value = mb_substr($v, 0, $maxString, 'UTF-8');
+ } else {
+ continue 2;
+ }
+ $a = null;
+ break;
+
+ case \is_array($v):
+ if (!$v) {
+ continue 2;
+ }
+ $stub = $arrayStub;
+ $stub->class = Stub::ARRAY_INDEXED;
+
+ $j = -1;
+ foreach ($v as $gk => $gv) {
+ if ($gk !== ++$j) {
+ $stub->class = Stub::ARRAY_ASSOC;
+ break;
+ }
+ }
+ $a = $v;
+
+ if (Stub::ARRAY_ASSOC === $stub->class) {
+ // Copies of $GLOBALS have very strange behavior,
+ // let's detect them with some black magic
+ if (\PHP_VERSION_ID < 80100 && ($a[$gid] = true) && isset($v[$gid])) {
+ unset($v[$gid]);
+ $a = [];
+ foreach ($v as $gk => &$gv) {
+ if ($v === $gv) {
+ unset($v);
+ $v = new Stub();
+ $v->value = [$v->cut = \count($gv), Stub::TYPE_ARRAY => 0];
+ $v->handle = -1;
+ $gv = &$hardRefs[spl_object_id($v)];
+ $gv = $v;
+ }
+
+ $a[$gk] = &$gv;
+ }
+ unset($gv);
+ } else {
+ $a = $v;
+ }
+ } elseif (\PHP_VERSION_ID < 70200) {
+ $indexedArrays[$len] = true;
+ }
+ break;
+
+ case \is_object($v):
+ case $v instanceof \__PHP_Incomplete_Class:
+ if (empty($objRefs[$h = spl_object_id($v)])) {
+ $stub = new Stub();
+ $stub->type = Stub::TYPE_OBJECT;
+ $stub->class = \get_class($v);
+ $stub->value = $v;
+ $stub->handle = $h;
+ $a = $this->castObject($stub, 0 < $i);
+ if ($v !== $stub->value) {
+ if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) {
+ break;
+ }
+ $stub->handle = $h = spl_object_id($stub->value);
+ }
+ $stub->value = null;
+ if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) {
+ $stub->cut = \count($a);
+ $a = null;
+ }
+ }
+ if (empty($objRefs[$h])) {
+ $objRefs[$h] = $stub;
+ $objects[] = $v;
+ } else {
+ $stub = $objRefs[$h];
+ ++$stub->refCount;
+ $a = null;
+ }
+ break;
+
+ default: // resource
+ if (empty($resRefs[$h = (int) $v])) {
+ $stub = new Stub();
+ $stub->type = Stub::TYPE_RESOURCE;
+ if ('Unknown' === $stub->class = @get_resource_type($v)) {
+ $stub->class = 'Closed';
+ }
+ $stub->value = $v;
+ $stub->handle = $h;
+ $a = $this->castResource($stub, 0 < $i);
+ $stub->value = null;
+ if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) {
+ $stub->cut = \count($a);
+ $a = null;
+ }
+ }
+ if (empty($resRefs[$h])) {
+ $resRefs[$h] = $stub;
+ } else {
+ $stub = $resRefs[$h];
+ ++$stub->refCount;
+ $a = null;
+ }
+ break;
+ }
+
+ if ($a) {
+ if (!$minimumDepthReached || 0 > $maxItems) {
+ $queue[$len] = $a;
+ $stub->position = $len++;
+ } elseif ($pos < $maxItems) {
+ if ($maxItems < $pos += \count($a)) {
+ $a = \array_slice($a, 0, $maxItems - $pos, true);
+ if ($stub->cut >= 0) {
+ $stub->cut += $pos - $maxItems;
+ }
+ }
+ $queue[$len] = $a;
+ $stub->position = $len++;
+ } elseif ($stub->cut >= 0) {
+ $stub->cut += \count($a);
+ $stub->position = 0;
+ }
+ }
+
+ if ($arrayStub === $stub) {
+ if ($arrayStub->cut) {
+ $stub = [$arrayStub->cut, $arrayStub->class => $arrayStub->position];
+ $arrayStub->cut = 0;
+ } elseif (isset(self::$arrayCache[$arrayStub->class][$arrayStub->position])) {
+ $stub = self::$arrayCache[$arrayStub->class][$arrayStub->position];
+ } else {
+ self::$arrayCache[$arrayStub->class][$arrayStub->position] = $stub = [$arrayStub->class => $arrayStub->position];
+ }
+ }
+
+ if ($zvalIsRef) {
+ $refs[$k]->value = $stub;
+ } else {
+ $vals[$k] = $stub;
+ }
+ }
+
+ if ($fromObjCast) {
+ $fromObjCast = false;
+ $refs = $vals;
+ $vals = [];
+ $j = -1;
+ foreach ($queue[$i] as $k => $v) {
+ foreach ([$k => true] as $gk => $gv) {
+ }
+ if ($gk !== $k) {
+ $vals = (object) $vals;
+ $vals->{$k} = $refs[++$j];
+ $vals = (array) $vals;
+ } else {
+ $vals[$k] = $refs[++$j];
+ }
+ }
+ }
+
+ $queue[$i] = $vals;
+ }
+
+ foreach ($values as $h => $v) {
+ $hardRefs[$h] = $v;
+ }
+
+ return $queue;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php b/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php
new file mode 100644
index 000000000..7d9ec0e7e
--- /dev/null
+++ b/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php
@@ -0,0 +1,88 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Command\Descriptor;
+
+use Symfony\Component\Console\Formatter\OutputFormatterStyle;
+use Symfony\Component\Console\Input\ArrayInput;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+/**
+ * Describe collected data clones for cli output.
+ *
+ * @author Maxime Steinhausser
+ *
+ * @final
+ */
+class CliDescriptor implements DumpDescriptorInterface
+{
+ private $dumper;
+ private $lastIdentifier;
+ private $supportsHref;
+
+ public function __construct(CliDumper $dumper)
+ {
+ $this->dumper = $dumper;
+ $this->supportsHref = method_exists(OutputFormatterStyle::class, 'setHref');
+ }
+
+ public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void
+ {
+ $io = $output instanceof SymfonyStyle ? $output : new SymfonyStyle(new ArrayInput([]), $output);
+ $this->dumper->setColors($output->isDecorated());
+
+ $rows = [['date', date('r', (int) $context['timestamp'])]];
+ $lastIdentifier = $this->lastIdentifier;
+ $this->lastIdentifier = $clientId;
+
+ $section = "Received from client #$clientId";
+ if (isset($context['request'])) {
+ $request = $context['request'];
+ $this->lastIdentifier = $request['identifier'];
+ $section = sprintf('%s %s', $request['method'], $request['uri']);
+ if ($controller = $request['controller']) {
+ $rows[] = ['controller', rtrim($this->dumper->dump($controller, true), "\n")];
+ }
+ } elseif (isset($context['cli'])) {
+ $this->lastIdentifier = $context['cli']['identifier'];
+ $section = '$ '.$context['cli']['command_line'];
+ }
+
+ if ($this->lastIdentifier !== $lastIdentifier) {
+ $io->section($section);
+ }
+
+ if (isset($context['source'])) {
+ $source = $context['source'];
+ $sourceInfo = sprintf('%s on line %d', $source['name'], $source['line']);
+ $fileLink = $source['file_link'] ?? null;
+ if ($this->supportsHref && $fileLink) {
+ $sourceInfo = sprintf('%s>', $fileLink, $sourceInfo);
+ }
+ $rows[] = ['source', $sourceInfo];
+ $file = $source['file_relative'] ?? $source['file'];
+ $rows[] = ['file', $file];
+ }
+
+ $io->table([], $rows);
+
+ if (!$this->supportsHref && isset($fileLink)) {
+ $io->writeln(['Open source in your IDE/browser: ', $fileLink]);
+ $io->newLine();
+ }
+
+ $this->dumper->dump($data);
+ $io->newLine();
+ }
+}
diff --git a/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php b/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php
new file mode 100644
index 000000000..267d27bfa
--- /dev/null
+++ b/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Command\Descriptor;
+
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\VarDumper\Cloner\Data;
+
+/**
+ * @author Maxime Steinhausser
+ */
+interface DumpDescriptorInterface
+{
+ public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void;
+}
diff --git a/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php b/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php
new file mode 100644
index 000000000..636b61828
--- /dev/null
+++ b/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php
@@ -0,0 +1,119 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Command\Descriptor;
+
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+
+/**
+ * Describe collected data clones for html output.
+ *
+ * @author Maxime Steinhausser
+ *
+ * @final
+ */
+class HtmlDescriptor implements DumpDescriptorInterface
+{
+ private $dumper;
+ private $initialized = false;
+
+ public function __construct(HtmlDumper $dumper)
+ {
+ $this->dumper = $dumper;
+ }
+
+ public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void
+ {
+ if (!$this->initialized) {
+ $styles = file_get_contents(__DIR__.'/../../Resources/css/htmlDescriptor.css');
+ $scripts = file_get_contents(__DIR__.'/../../Resources/js/htmlDescriptor.js');
+ $output->writeln("");
+ $this->initialized = true;
+ }
+
+ $title = '-';
+ if (isset($context['request'])) {
+ $request = $context['request'];
+ $controller = "{$this->dumper->dump($request['controller'], true, ['maxDepth' => 0])} ";
+ $title = sprintf('%s %s ', $request['method'], $uri = $request['uri'], $uri);
+ $dedupIdentifier = $request['identifier'];
+ } elseif (isset($context['cli'])) {
+ $title = '$ '.$context['cli']['command_line'];
+ $dedupIdentifier = $context['cli']['identifier'];
+ } else {
+ $dedupIdentifier = uniqid('', true);
+ }
+
+ $sourceDescription = '';
+ if (isset($context['source'])) {
+ $source = $context['source'];
+ $projectDir = $source['project_dir'] ?? null;
+ $sourceDescription = sprintf('%s on line %d', $source['name'], $source['line']);
+ if (isset($source['file_link'])) {
+ $sourceDescription = sprintf('%s ', $source['file_link'], $sourceDescription);
+ }
+ }
+
+ $isoDate = $this->extractDate($context, 'c');
+ $tags = array_filter([
+ 'controller' => $controller ?? null,
+ 'project dir' => $projectDir ?? null,
+ ]);
+
+ $output->writeln(<<
+
+
+
+ $sourceDescription
+
+ {$this->dumper->dump($data, true)}
+
+
+HTML
+ );
+ }
+
+ private function extractDate(array $context, string $format = 'r'): string
+ {
+ return date($format, (int) $context['timestamp']);
+ }
+
+ private function renderTags(array $tags): string
+ {
+ if (!$tags) {
+ return '';
+ }
+
+ $renderedTags = '';
+ foreach ($tags as $key => $value) {
+ $renderedTags .= sprintf('%s %s ', $key, $value);
+ }
+
+ return <<
+
+
+HTML;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Command/ServerDumpCommand.php b/vendor/symfony/var-dumper/Command/ServerDumpCommand.php
new file mode 100644
index 000000000..b66301b52
--- /dev/null
+++ b/vendor/symfony/var-dumper/Command/ServerDumpCommand.php
@@ -0,0 +1,101 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Command;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor;
+use Symfony\Component\VarDumper\Command\Descriptor\DumpDescriptorInterface;
+use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+use Symfony\Component\VarDumper\Server\DumpServer;
+
+/**
+ * Starts a dump server to collect and output dumps on a single place with multiple formats support.
+ *
+ * @author Maxime Steinhausser
+ *
+ * @final
+ */
+class ServerDumpCommand extends Command
+{
+ protected static $defaultName = 'server:dump';
+
+ private $server;
+
+ /** @var DumpDescriptorInterface[] */
+ private $descriptors;
+
+ public function __construct(DumpServer $server, array $descriptors = [])
+ {
+ $this->server = $server;
+ $this->descriptors = $descriptors + [
+ 'cli' => new CliDescriptor(new CliDumper()),
+ 'html' => new HtmlDescriptor(new HtmlDumper()),
+ ];
+
+ parent::__construct();
+ }
+
+ protected function configure()
+ {
+ $availableFormats = implode(', ', array_keys($this->descriptors));
+
+ $this
+ ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format (%s)', $availableFormats), 'cli')
+ ->setDescription('Start a dump server that collects and displays dumps in a single place')
+ ->setHelp(<<<'EOF'
+%command.name% starts a dump server that collects and displays
+dumps in a single place for debugging you application:
+
+ php %command.full_name%
+
+You can consult dumped data in HTML format in your browser by providing the --format=html option
+and redirecting the output to a file:
+
+ php %command.full_name% --format="html" > dump.html
+
+EOF
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $io = new SymfonyStyle($input, $output);
+ $format = $input->getOption('format');
+
+ if (!$descriptor = $this->descriptors[$format] ?? null) {
+ throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $format));
+ }
+
+ $errorIo = $io->getErrorStyle();
+ $errorIo->title('Symfony Var Dumper Server');
+
+ $this->server->start();
+
+ $errorIo->success(sprintf('Server listening on %s', $this->server->getHost()));
+ $errorIo->comment('Quit the server with CONTROL-C.');
+
+ $this->server->listen(function (Data $data, array $context, int $clientId) use ($descriptor, $io) {
+ $descriptor->describe($io, $data, $context, $clientId);
+ });
+
+ return 0;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Dumper/AbstractDumper.php b/vendor/symfony/var-dumper/Dumper/AbstractDumper.php
new file mode 100644
index 000000000..eea56b599
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/AbstractDumper.php
@@ -0,0 +1,212 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper;
+
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Cloner\DumperInterface;
+
+/**
+ * Abstract mechanism for dumping a Data object.
+ *
+ * @author Nicolas Grekas
+ */
+abstract class AbstractDumper implements DataDumperInterface, DumperInterface
+{
+ public const DUMP_LIGHT_ARRAY = 1;
+ public const DUMP_STRING_LENGTH = 2;
+ public const DUMP_COMMA_SEPARATOR = 4;
+ public const DUMP_TRAILING_COMMA = 8;
+
+ public static $defaultOutput = 'php://output';
+
+ protected $line = '';
+ protected $lineDumper;
+ protected $outputStream;
+ protected $decimalPoint; // This is locale dependent
+ protected $indentPad = ' ';
+ protected $flags;
+
+ private $charset = '';
+
+ /**
+ * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutput
+ * @param string|null $charset The default character encoding to use for non-UTF8 strings
+ * @param int $flags A bit field of static::DUMP_* constants to fine tune dumps representation
+ */
+ public function __construct($output = null, string $charset = null, int $flags = 0)
+ {
+ $this->flags = $flags;
+ $this->setCharset($charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8');
+ $this->decimalPoint = localeconv();
+ $this->decimalPoint = $this->decimalPoint['decimal_point'];
+ $this->setOutput($output ?: static::$defaultOutput);
+ if (!$output && \is_string(static::$defaultOutput)) {
+ static::$defaultOutput = $this->outputStream;
+ }
+ }
+
+ /**
+ * Sets the output destination of the dumps.
+ *
+ * @param callable|resource|string $output A line dumper callable, an opened stream or an output path
+ *
+ * @return callable|resource|string The previous output destination
+ */
+ public function setOutput($output)
+ {
+ $prev = null !== $this->outputStream ? $this->outputStream : $this->lineDumper;
+
+ if (\is_callable($output)) {
+ $this->outputStream = null;
+ $this->lineDumper = $output;
+ } else {
+ if (\is_string($output)) {
+ $output = fopen($output, 'w');
+ }
+ $this->outputStream = $output;
+ $this->lineDumper = [$this, 'echoLine'];
+ }
+
+ return $prev;
+ }
+
+ /**
+ * Sets the default character encoding to use for non-UTF8 strings.
+ *
+ * @param string $charset The default character encoding to use for non-UTF8 strings
+ *
+ * @return string The previous charset
+ */
+ public function setCharset($charset)
+ {
+ $prev = $this->charset;
+
+ $charset = strtoupper($charset);
+ $charset = null === $charset || 'UTF-8' === $charset || 'UTF8' === $charset ? 'CP1252' : $charset;
+
+ $this->charset = $charset;
+
+ return $prev;
+ }
+
+ /**
+ * Sets the indentation pad string.
+ *
+ * @param string $pad A string that will be prepended to dumped lines, repeated by nesting level
+ *
+ * @return string The previous indent pad
+ */
+ public function setIndentPad($pad)
+ {
+ $prev = $this->indentPad;
+ $this->indentPad = $pad;
+
+ return $prev;
+ }
+
+ /**
+ * Dumps a Data object.
+ *
+ * @param callable|resource|string|true|null $output A line dumper callable, an opened stream, an output path or true to return the dump
+ *
+ * @return string|null The dump as string when $output is true
+ */
+ public function dump(Data $data, $output = null)
+ {
+ $this->decimalPoint = localeconv();
+ $this->decimalPoint = $this->decimalPoint['decimal_point'];
+
+ if ($locale = $this->flags & (self::DUMP_COMMA_SEPARATOR | self::DUMP_TRAILING_COMMA) ? setlocale(\LC_NUMERIC, 0) : null) {
+ setlocale(\LC_NUMERIC, 'C');
+ }
+
+ if ($returnDump = true === $output) {
+ $output = fopen('php://memory', 'r+');
+ }
+ if ($output) {
+ $prevOutput = $this->setOutput($output);
+ }
+ try {
+ $data->dump($this);
+ $this->dumpLine(-1);
+
+ if ($returnDump) {
+ $result = stream_get_contents($output, -1, 0);
+ fclose($output);
+
+ return $result;
+ }
+ } finally {
+ if ($output) {
+ $this->setOutput($prevOutput);
+ }
+ if ($locale) {
+ setlocale(\LC_NUMERIC, $locale);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Dumps the current line.
+ *
+ * @param int $depth The recursive depth in the dumped structure for the line being dumped,
+ * or -1 to signal the end-of-dump to the line dumper callable
+ */
+ protected function dumpLine($depth)
+ {
+ ($this->lineDumper)($this->line, $depth, $this->indentPad);
+ $this->line = '';
+ }
+
+ /**
+ * Generic line dumper callback.
+ *
+ * @param string $line The line to write
+ * @param int $depth The recursive depth in the dumped structure
+ * @param string $indentPad The line indent pad
+ */
+ protected function echoLine($line, $depth, $indentPad)
+ {
+ if (-1 !== $depth) {
+ fwrite($this->outputStream, str_repeat($indentPad, $depth).$line."\n");
+ }
+ }
+
+ /**
+ * Converts a non-UTF-8 string to UTF-8.
+ *
+ * @param string|null $s The non-UTF-8 string to convert
+ *
+ * @return string|null The string converted to UTF-8
+ */
+ protected function utf8Encode($s)
+ {
+ if (null === $s || preg_match('//u', $s)) {
+ return $s;
+ }
+
+ if (!\function_exists('iconv')) {
+ throw new \RuntimeException('Unable to convert a non-UTF-8 string to UTF-8: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.');
+ }
+
+ if (false !== $c = @iconv($this->charset, 'UTF-8', $s)) {
+ return $c;
+ }
+ if ('CP1252' !== $this->charset && false !== $c = @iconv('CP1252', 'UTF-8', $s)) {
+ return $c;
+ }
+
+ return iconv('CP850', 'UTF-8', $s);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Dumper/CliDumper.php b/vendor/symfony/var-dumper/Dumper/CliDumper.php
new file mode 100644
index 000000000..55484b0b0
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/CliDumper.php
@@ -0,0 +1,655 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper;
+
+use Symfony\Component\VarDumper\Cloner\Cursor;
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * CliDumper dumps variables for command line output.
+ *
+ * @author Nicolas Grekas
+ */
+class CliDumper extends AbstractDumper
+{
+ public static $defaultColors;
+ public static $defaultOutput = 'php://stdout';
+
+ protected $colors;
+ protected $maxStringWidth = 0;
+ protected $styles = [
+ // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
+ 'default' => '0;38;5;208',
+ 'num' => '1;38;5;38',
+ 'const' => '1;38;5;208',
+ 'str' => '1;38;5;113',
+ 'note' => '38;5;38',
+ 'ref' => '38;5;247',
+ 'public' => '',
+ 'protected' => '',
+ 'private' => '',
+ 'meta' => '38;5;170',
+ 'key' => '38;5;113',
+ 'index' => '38;5;38',
+ ];
+
+ protected static $controlCharsRx = '/[\x00-\x1F\x7F]+/';
+ protected static $controlCharsMap = [
+ "\t" => '\t',
+ "\n" => '\n',
+ "\v" => '\v',
+ "\f" => '\f',
+ "\r" => '\r',
+ "\033" => '\e',
+ ];
+
+ protected $collapseNextHash = false;
+ protected $expandNextHash = false;
+
+ private $displayOptions = [
+ 'fileLinkFormat' => null,
+ ];
+
+ private $handlesHrefGracefully;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct($output = null, string $charset = null, int $flags = 0)
+ {
+ parent::__construct($output, $charset, $flags);
+
+ if ('\\' === \DIRECTORY_SEPARATOR && !$this->isWindowsTrueColor()) {
+ // Use only the base 16 xterm colors when using ANSICON or standard Windows 10 CLI
+ $this->setStyles([
+ 'default' => '31',
+ 'num' => '1;34',
+ 'const' => '1;31',
+ 'str' => '1;32',
+ 'note' => '34',
+ 'ref' => '1;30',
+ 'meta' => '35',
+ 'key' => '32',
+ 'index' => '34',
+ ]);
+ }
+
+ $this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l';
+ }
+
+ /**
+ * Enables/disables colored output.
+ *
+ * @param bool $colors
+ */
+ public function setColors($colors)
+ {
+ $this->colors = (bool) $colors;
+ }
+
+ /**
+ * Sets the maximum number of characters per line for dumped strings.
+ *
+ * @param int $maxStringWidth
+ */
+ public function setMaxStringWidth($maxStringWidth)
+ {
+ $this->maxStringWidth = (int) $maxStringWidth;
+ }
+
+ /**
+ * Configures styles.
+ *
+ * @param array $styles A map of style names to style definitions
+ */
+ public function setStyles(array $styles)
+ {
+ $this->styles = $styles + $this->styles;
+ }
+
+ /**
+ * Configures display options.
+ *
+ * @param array $displayOptions A map of display options to customize the behavior
+ */
+ public function setDisplayOptions(array $displayOptions)
+ {
+ $this->displayOptions = $displayOptions + $this->displayOptions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dumpScalar(Cursor $cursor, $type, $value)
+ {
+ $this->dumpKey($cursor);
+
+ $style = 'const';
+ $attr = $cursor->attr;
+
+ switch ($type) {
+ case 'default':
+ $style = 'default';
+ break;
+
+ case 'integer':
+ $style = 'num';
+ break;
+
+ case 'double':
+ $style = 'num';
+
+ switch (true) {
+ case \INF === $value: $value = 'INF'; break;
+ case -\INF === $value: $value = '-INF'; break;
+ case is_nan($value): $value = 'NAN'; break;
+ default:
+ $value = (string) $value;
+ if (false === strpos($value, $this->decimalPoint)) {
+ $value .= $this->decimalPoint.'0';
+ }
+ break;
+ }
+ break;
+
+ case 'NULL':
+ $value = 'null';
+ break;
+
+ case 'boolean':
+ $value = $value ? 'true' : 'false';
+ break;
+
+ default:
+ $attr += ['value' => $this->utf8Encode($value)];
+ $value = $this->utf8Encode($type);
+ break;
+ }
+
+ $this->line .= $this->style($style, $value, $attr);
+
+ $this->endValue($cursor);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dumpString(Cursor $cursor, $str, $bin, $cut)
+ {
+ $this->dumpKey($cursor);
+ $attr = $cursor->attr;
+
+ if ($bin) {
+ $str = $this->utf8Encode($str);
+ }
+ if ('' === $str) {
+ $this->line .= '""';
+ $this->endValue($cursor);
+ } else {
+ $attr += [
+ 'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0,
+ 'binary' => $bin,
+ ];
+ $str = explode("\n", $str);
+ if (isset($str[1]) && !isset($str[2]) && !isset($str[1][0])) {
+ unset($str[1]);
+ $str[0] .= "\n";
+ }
+ $m = \count($str) - 1;
+ $i = $lineCut = 0;
+
+ if (self::DUMP_STRING_LENGTH & $this->flags) {
+ $this->line .= '('.$attr['length'].') ';
+ }
+ if ($bin) {
+ $this->line .= 'b';
+ }
+
+ if ($m) {
+ $this->line .= '"""';
+ $this->dumpLine($cursor->depth);
+ } else {
+ $this->line .= '"';
+ }
+
+ foreach ($str as $str) {
+ if ($i < $m) {
+ $str .= "\n";
+ }
+ if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = mb_strlen($str, 'UTF-8')) {
+ $str = mb_substr($str, 0, $this->maxStringWidth, 'UTF-8');
+ $lineCut = $len - $this->maxStringWidth;
+ }
+ if ($m && 0 < $cursor->depth) {
+ $this->line .= $this->indentPad;
+ }
+ if ('' !== $str) {
+ $this->line .= $this->style('str', $str, $attr);
+ }
+ if ($i++ == $m) {
+ if ($m) {
+ if ('' !== $str) {
+ $this->dumpLine($cursor->depth);
+ if (0 < $cursor->depth) {
+ $this->line .= $this->indentPad;
+ }
+ }
+ $this->line .= '"""';
+ } else {
+ $this->line .= '"';
+ }
+ if ($cut < 0) {
+ $this->line .= '…';
+ $lineCut = 0;
+ } elseif ($cut) {
+ $lineCut += $cut;
+ }
+ }
+ if ($lineCut) {
+ $this->line .= '…'.$lineCut;
+ $lineCut = 0;
+ }
+
+ if ($i > $m) {
+ $this->endValue($cursor);
+ } else {
+ $this->dumpLine($cursor->depth);
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function enterHash(Cursor $cursor, $type, $class, $hasChild)
+ {
+ if (null === $this->colors) {
+ $this->colors = $this->supportsColors();
+ }
+
+ $this->dumpKey($cursor);
+ $attr = $cursor->attr;
+
+ if ($this->collapseNextHash) {
+ $cursor->skipChildren = true;
+ $this->collapseNextHash = $hasChild = false;
+ }
+
+ $class = $this->utf8Encode($class);
+ if (Cursor::HASH_OBJECT === $type) {
+ $prefix = $class && 'stdClass' !== $class ? $this->style('note', $class, $attr).(empty($attr['cut_hash']) ? ' {' : '') : '{';
+ } elseif (Cursor::HASH_RESOURCE === $type) {
+ $prefix = $this->style('note', $class.' resource', $attr).($hasChild ? ' {' : ' ');
+ } else {
+ $prefix = $class && !(self::DUMP_LIGHT_ARRAY & $this->flags) ? $this->style('note', 'array:'.$class).' [' : '[';
+ }
+
+ if (($cursor->softRefCount || 0 < $cursor->softRefHandle) && empty($attr['cut_hash'])) {
+ $prefix .= $this->style('ref', (Cursor::HASH_RESOURCE === $type ? '@' : '#').(0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->softRefTo), ['count' => $cursor->softRefCount]);
+ } elseif ($cursor->hardRefTo && !$cursor->refIndex && $class) {
+ $prefix .= $this->style('ref', '&'.$cursor->hardRefTo, ['count' => $cursor->hardRefCount]);
+ } elseif (!$hasChild && Cursor::HASH_RESOURCE === $type) {
+ $prefix = substr($prefix, 0, -1);
+ }
+
+ $this->line .= $prefix;
+
+ if ($hasChild) {
+ $this->dumpLine($cursor->depth);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut)
+ {
+ if (empty($cursor->attr['cut_hash'])) {
+ $this->dumpEllipsis($cursor, $hasChild, $cut);
+ $this->line .= Cursor::HASH_OBJECT === $type ? '}' : (Cursor::HASH_RESOURCE !== $type ? ']' : ($hasChild ? '}' : ''));
+ }
+
+ $this->endValue($cursor);
+ }
+
+ /**
+ * Dumps an ellipsis for cut children.
+ *
+ * @param bool $hasChild When the dump of the hash has child item
+ * @param int $cut The number of items the hash has been cut by
+ */
+ protected function dumpEllipsis(Cursor $cursor, $hasChild, $cut)
+ {
+ if ($cut) {
+ $this->line .= ' …';
+ if (0 < $cut) {
+ $this->line .= $cut;
+ }
+ if ($hasChild) {
+ $this->dumpLine($cursor->depth + 1);
+ }
+ }
+ }
+
+ /**
+ * Dumps a key in a hash structure.
+ */
+ protected function dumpKey(Cursor $cursor)
+ {
+ if (null !== $key = $cursor->hashKey) {
+ if ($cursor->hashKeyIsBinary) {
+ $key = $this->utf8Encode($key);
+ }
+ $attr = ['binary' => $cursor->hashKeyIsBinary];
+ $bin = $cursor->hashKeyIsBinary ? 'b' : '';
+ $style = 'key';
+ switch ($cursor->hashType) {
+ default:
+ case Cursor::HASH_INDEXED:
+ if (self::DUMP_LIGHT_ARRAY & $this->flags) {
+ break;
+ }
+ $style = 'index';
+ // no break
+ case Cursor::HASH_ASSOC:
+ if (\is_int($key)) {
+ $this->line .= $this->style($style, $key).' => ';
+ } else {
+ $this->line .= $bin.'"'.$this->style($style, $key).'" => ';
+ }
+ break;
+
+ case Cursor::HASH_RESOURCE:
+ $key = "\0~\0".$key;
+ // no break
+ case Cursor::HASH_OBJECT:
+ if (!isset($key[0]) || "\0" !== $key[0]) {
+ $this->line .= '+'.$bin.$this->style('public', $key).': ';
+ } elseif (0 < strpos($key, "\0", 1)) {
+ $key = explode("\0", substr($key, 1), 2);
+
+ switch ($key[0][0]) {
+ case '+': // User inserted keys
+ $attr['dynamic'] = true;
+ $this->line .= '+'.$bin.'"'.$this->style('public', $key[1], $attr).'": ';
+ break 2;
+ case '~':
+ $style = 'meta';
+ if (isset($key[0][1])) {
+ parse_str(substr($key[0], 1), $attr);
+ $attr += ['binary' => $cursor->hashKeyIsBinary];
+ }
+ break;
+ case '*':
+ $style = 'protected';
+ $bin = '#'.$bin;
+ break;
+ default:
+ $attr['class'] = $key[0];
+ $style = 'private';
+ $bin = '-'.$bin;
+ break;
+ }
+
+ if (isset($attr['collapse'])) {
+ if ($attr['collapse']) {
+ $this->collapseNextHash = true;
+ } else {
+ $this->expandNextHash = true;
+ }
+ }
+
+ $this->line .= $bin.$this->style($style, $key[1], $attr).($attr['separator'] ?? ': ');
+ } else {
+ // This case should not happen
+ $this->line .= '-'.$bin.'"'.$this->style('private', $key, ['class' => '']).'": ';
+ }
+ break;
+ }
+
+ if ($cursor->hardRefTo) {
+ $this->line .= $this->style('ref', '&'.($cursor->hardRefCount ? $cursor->hardRefTo : ''), ['count' => $cursor->hardRefCount]).' ';
+ }
+ }
+ }
+
+ /**
+ * Decorates a value with some style.
+ *
+ * @param string $style The type of style being applied
+ * @param string $value The value being styled
+ * @param array $attr Optional context information
+ *
+ * @return string The value with style decoration
+ */
+ protected function style($style, $value, $attr = [])
+ {
+ if (null === $this->colors) {
+ $this->colors = $this->supportsColors();
+ }
+
+ if (null === $this->handlesHrefGracefully) {
+ $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR')
+ && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100);
+ }
+
+ if (isset($attr['ellipsis'], $attr['ellipsis-type'])) {
+ $prefix = substr($value, 0, -$attr['ellipsis']);
+ if ('cli' === \PHP_SAPI && 'path' === $attr['ellipsis-type'] && isset($_SERVER[$pwd = '\\' === \DIRECTORY_SEPARATOR ? 'CD' : 'PWD']) && 0 === strpos($prefix, $_SERVER[$pwd])) {
+ $prefix = '.'.substr($prefix, \strlen($_SERVER[$pwd]));
+ }
+ if (!empty($attr['ellipsis-tail'])) {
+ $prefix .= substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']);
+ $value = substr($value, -$attr['ellipsis'] + $attr['ellipsis-tail']);
+ } else {
+ $value = substr($value, -$attr['ellipsis']);
+ }
+
+ $value = $this->style('default', $prefix).$this->style($style, $value);
+
+ goto href;
+ }
+
+ $map = static::$controlCharsMap;
+ $startCchr = $this->colors ? "\033[m\033[{$this->styles['default']}m" : '';
+ $endCchr = $this->colors ? "\033[m\033[{$this->styles[$style]}m" : '';
+ $value = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $startCchr, $endCchr) {
+ $s = $startCchr;
+ $c = $c[$i = 0];
+ do {
+ $s .= $map[$c[$i]] ?? sprintf('\x%02X', \ord($c[$i]));
+ } while (isset($c[++$i]));
+
+ return $s.$endCchr;
+ }, $value, -1, $cchrCount);
+
+ if ($this->colors) {
+ if ($cchrCount && "\033" === $value[0]) {
+ $value = substr($value, \strlen($startCchr));
+ } else {
+ $value = "\033[{$this->styles[$style]}m".$value;
+ }
+ if ($cchrCount && $endCchr === substr($value, -\strlen($endCchr))) {
+ $value = substr($value, 0, -\strlen($endCchr));
+ } else {
+ $value .= "\033[{$this->styles['default']}m";
+ }
+ }
+
+ href:
+ if ($this->colors && $this->handlesHrefGracefully) {
+ if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) {
+ if ('note' === $style) {
+ $value .= "\033]8;;{$href}\033\\^\033]8;;\033\\";
+ } else {
+ $attr['href'] = $href;
+ }
+ }
+ if (isset($attr['href'])) {
+ $value = "\033]8;;{$attr['href']}\033\\{$value}\033]8;;\033\\";
+ }
+ } elseif ($attr['if_links'] ?? false) {
+ return '';
+ }
+
+ return $value;
+ }
+
+ /**
+ * @return bool Tells if the current output stream supports ANSI colors or not
+ */
+ protected function supportsColors()
+ {
+ if ($this->outputStream !== static::$defaultOutput) {
+ return $this->hasColorSupport($this->outputStream);
+ }
+ if (null !== static::$defaultColors) {
+ return static::$defaultColors;
+ }
+ if (isset($_SERVER['argv'][1])) {
+ $colors = $_SERVER['argv'];
+ $i = \count($colors);
+ while (--$i > 0) {
+ if (isset($colors[$i][5])) {
+ switch ($colors[$i]) {
+ case '--ansi':
+ case '--color':
+ case '--color=yes':
+ case '--color=force':
+ case '--color=always':
+ return static::$defaultColors = true;
+
+ case '--no-ansi':
+ case '--color=no':
+ case '--color=none':
+ case '--color=never':
+ return static::$defaultColors = false;
+ }
+ }
+ }
+ }
+
+ $h = stream_get_meta_data($this->outputStream) + ['wrapper_type' => null];
+ $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'w') : $this->outputStream;
+
+ return static::$defaultColors = $this->hasColorSupport($h);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function dumpLine($depth, $endOfValue = false)
+ {
+ if ($this->colors) {
+ $this->line = sprintf("\033[%sm%s\033[m", $this->styles['default'], $this->line);
+ }
+ parent::dumpLine($depth);
+ }
+
+ protected function endValue(Cursor $cursor)
+ {
+ if (-1 === $cursor->hashType) {
+ return;
+ }
+
+ if (Stub::ARRAY_INDEXED === $cursor->hashType || Stub::ARRAY_ASSOC === $cursor->hashType) {
+ if (self::DUMP_TRAILING_COMMA & $this->flags && 0 < $cursor->depth) {
+ $this->line .= ',';
+ } elseif (self::DUMP_COMMA_SEPARATOR & $this->flags && 1 < $cursor->hashLength - $cursor->hashIndex) {
+ $this->line .= ',';
+ }
+ }
+
+ $this->dumpLine($cursor->depth, true);
+ }
+
+ /**
+ * Returns true if the stream supports colorization.
+ *
+ * Reference: Composer\XdebugHandler\Process::supportsColor
+ * https://github.com/composer/xdebug-handler
+ *
+ * @param mixed $stream A CLI output stream
+ */
+ private function hasColorSupport($stream): bool
+ {
+ if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) {
+ return false;
+ }
+
+ // Follow https://no-color.org/
+ if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) {
+ return false;
+ }
+
+ if ('Hyper' === getenv('TERM_PROGRAM')) {
+ return true;
+ }
+
+ if (\DIRECTORY_SEPARATOR === '\\') {
+ return (\function_exists('sapi_windows_vt100_support')
+ && @sapi_windows_vt100_support($stream))
+ || false !== getenv('ANSICON')
+ || 'ON' === getenv('ConEmuANSI')
+ || 'xterm' === getenv('TERM');
+ }
+
+ if (\function_exists('stream_isatty')) {
+ return @stream_isatty($stream);
+ }
+
+ if (\function_exists('posix_isatty')) {
+ return @posix_isatty($stream);
+ }
+
+ $stat = @fstat($stream);
+ // Check if formatted mode is S_IFCHR
+ return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
+ }
+
+ /**
+ * Returns true if the Windows terminal supports true color.
+ *
+ * Note that this does not check an output stream, but relies on environment
+ * variables from known implementations, or a PHP and Windows version that
+ * supports true color.
+ */
+ private function isWindowsTrueColor(): bool
+ {
+ $result = 183 <= getenv('ANSICON_VER')
+ || 'ON' === getenv('ConEmuANSI')
+ || 'xterm' === getenv('TERM')
+ || 'Hyper' === getenv('TERM_PROGRAM');
+
+ if (!$result && \PHP_VERSION_ID >= 70200) {
+ $version = sprintf(
+ '%s.%s.%s',
+ PHP_WINDOWS_VERSION_MAJOR,
+ PHP_WINDOWS_VERSION_MINOR,
+ PHP_WINDOWS_VERSION_BUILD
+ );
+ $result = $version >= '10.0.15063';
+ }
+
+ return $result;
+ }
+
+ private function getSourceLink(string $file, int $line)
+ {
+ if ($fmt = $this->displayOptions['fileLinkFormat']) {
+ return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : ($fmt->format($file, $line) ?: 'file://'.$file.'#L'.$line);
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php
new file mode 100644
index 000000000..38f878971
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper\ContextProvider;
+
+/**
+ * Tries to provide context on CLI.
+ *
+ * @author Maxime Steinhausser
+ */
+final class CliContextProvider implements ContextProviderInterface
+{
+ public function getContext(): ?array
+ {
+ if ('cli' !== \PHP_SAPI) {
+ return null;
+ }
+
+ return [
+ 'command_line' => $commandLine = implode(' ', $_SERVER['argv'] ?? []),
+ 'identifier' => hash('crc32b', $commandLine.$_SERVER['REQUEST_TIME_FLOAT']),
+ ];
+ }
+}
diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php
new file mode 100644
index 000000000..38ef3b0f1
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper\ContextProvider;
+
+/**
+ * Interface to provide contextual data about dump data clones sent to a server.
+ *
+ * @author Maxime Steinhausser
+ */
+interface ContextProviderInterface
+{
+ /**
+ * @return array|null Context data or null if unable to provide any context
+ */
+ public function getContext(): ?array;
+}
diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php
new file mode 100644
index 000000000..3684a4753
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper\ContextProvider;
+
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\VarDumper\Caster\ReflectionCaster;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+
+/**
+ * Tries to provide context from a request.
+ *
+ * @author Maxime Steinhausser
+ */
+final class RequestContextProvider implements ContextProviderInterface
+{
+ private $requestStack;
+ private $cloner;
+
+ public function __construct(RequestStack $requestStack)
+ {
+ $this->requestStack = $requestStack;
+ $this->cloner = new VarCloner();
+ $this->cloner->setMaxItems(0);
+ $this->cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);
+ }
+
+ public function getContext(): ?array
+ {
+ if (null === $request = $this->requestStack->getCurrentRequest()) {
+ return null;
+ }
+
+ $controller = $request->attributes->get('_controller');
+
+ return [
+ 'uri' => $request->getUri(),
+ 'method' => $request->getMethod(),
+ 'controller' => $controller ? $this->cloner->cloneVar($controller) : $controller,
+ 'identifier' => spl_object_hash($request),
+ ];
+ }
+}
diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php
new file mode 100644
index 000000000..c3cd3221a
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php
@@ -0,0 +1,126 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper\ContextProvider;
+
+use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+use Symfony\Component\VarDumper\VarDumper;
+use Twig\Template;
+
+/**
+ * Tries to provide context from sources (class name, file, line, code excerpt, ...).
+ *
+ * @author Nicolas Grekas
+ * @author Maxime Steinhausser
+ */
+final class SourceContextProvider implements ContextProviderInterface
+{
+ private $limit;
+ private $charset;
+ private $projectDir;
+ private $fileLinkFormatter;
+
+ public function __construct(string $charset = null, string $projectDir = null, FileLinkFormatter $fileLinkFormatter = null, int $limit = 9)
+ {
+ $this->charset = $charset;
+ $this->projectDir = $projectDir;
+ $this->fileLinkFormatter = $fileLinkFormatter;
+ $this->limit = $limit;
+ }
+
+ public function getContext(): ?array
+ {
+ $trace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, $this->limit);
+
+ $file = $trace[1]['file'];
+ $line = $trace[1]['line'];
+ $name = false;
+ $fileExcerpt = false;
+
+ for ($i = 2; $i < $this->limit; ++$i) {
+ if (isset($trace[$i]['class'], $trace[$i]['function'])
+ && 'dump' === $trace[$i]['function']
+ && VarDumper::class === $trace[$i]['class']
+ ) {
+ $file = $trace[$i]['file'] ?? $file;
+ $line = $trace[$i]['line'] ?? $line;
+
+ while (++$i < $this->limit) {
+ if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && 0 !== strpos($trace[$i]['function'], 'call_user_func')) {
+ $file = $trace[$i]['file'];
+ $line = $trace[$i]['line'];
+
+ break;
+ } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) {
+ $template = $trace[$i]['object'];
+ $name = $template->getTemplateName();
+ $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false);
+ $info = $template->getDebugInfo();
+ if (isset($info[$trace[$i - 1]['line']])) {
+ $line = $info[$trace[$i - 1]['line']];
+ $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null;
+
+ if ($src) {
+ $src = explode("\n", $src);
+ $fileExcerpt = [];
+
+ for ($i = max($line - 3, 1), $max = min($line + 3, \count($src)); $i <= $max; ++$i) {
+ $fileExcerpt[] = ''.$this->htmlEncode($src[$i - 1]).' ';
+ }
+
+ $fileExcerpt = ''.implode("\n", $fileExcerpt).' ';
+ }
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ if (false === $name) {
+ $name = str_replace('\\', '/', $file);
+ $name = substr($name, strrpos($name, '/') + 1);
+ }
+
+ $context = ['name' => $name, 'file' => $file, 'line' => $line];
+ $context['file_excerpt'] = $fileExcerpt;
+
+ if (null !== $this->projectDir) {
+ $context['project_dir'] = $this->projectDir;
+ if (0 === strpos($file, $this->projectDir)) {
+ $context['file_relative'] = ltrim(substr($file, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR);
+ }
+ }
+
+ if ($this->fileLinkFormatter && $fileLink = $this->fileLinkFormatter->format($context['file'], $context['line'])) {
+ $context['file_link'] = $fileLink;
+ }
+
+ return $context;
+ }
+
+ private function htmlEncode(string $s): string
+ {
+ $html = '';
+
+ $dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset);
+ $dumper->setDumpHeader('');
+ $dumper->setDumpBoundaries('', '');
+
+ $cloner = new VarCloner();
+ $dumper->dump($cloner->cloneVar($s));
+
+ return substr(strip_tags($html), 1, -1);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php b/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php
new file mode 100644
index 000000000..76384176e
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper;
+
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
+
+/**
+ * @author Kévin Thérage
+ */
+class ContextualizedDumper implements DataDumperInterface
+{
+ private $wrappedDumper;
+ private $contextProviders;
+
+ /**
+ * @param ContextProviderInterface[] $contextProviders
+ */
+ public function __construct(DataDumperInterface $wrappedDumper, array $contextProviders)
+ {
+ $this->wrappedDumper = $wrappedDumper;
+ $this->contextProviders = $contextProviders;
+ }
+
+ public function dump(Data $data)
+ {
+ $context = [];
+ foreach ($this->contextProviders as $contextProvider) {
+ $context[\get_class($contextProvider)] = $contextProvider->getContext();
+ }
+
+ $this->wrappedDumper->dump($data->withContext($context));
+ }
+}
diff --git a/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php b/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php
new file mode 100644
index 000000000..b173bccf3
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper;
+
+use Symfony\Component\VarDumper\Cloner\Data;
+
+/**
+ * DataDumperInterface for dumping Data objects.
+ *
+ * @author Nicolas Grekas
+ */
+interface DataDumperInterface
+{
+ public function dump(Data $data);
+}
diff --git a/vendor/symfony/var-dumper/Dumper/HtmlDumper.php b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php
new file mode 100644
index 000000000..6b205a737
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php
@@ -0,0 +1,1004 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper;
+
+use Symfony\Component\VarDumper\Cloner\Cursor;
+use Symfony\Component\VarDumper\Cloner\Data;
+
+/**
+ * HtmlDumper dumps variables as HTML.
+ *
+ * @author Nicolas Grekas
+ */
+class HtmlDumper extends CliDumper
+{
+ public static $defaultOutput = 'php://output';
+
+ protected static $themes = [
+ 'dark' => [
+ 'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all',
+ 'num' => 'font-weight:bold; color:#1299DA',
+ 'const' => 'font-weight:bold',
+ 'str' => 'font-weight:bold; color:#56DB3A',
+ 'note' => 'color:#1299DA',
+ 'ref' => 'color:#A0A0A0',
+ 'public' => 'color:#FFFFFF',
+ 'protected' => 'color:#FFFFFF',
+ 'private' => 'color:#FFFFFF',
+ 'meta' => 'color:#B729D9',
+ 'key' => 'color:#56DB3A',
+ 'index' => 'color:#1299DA',
+ 'ellipsis' => 'color:#FF8400',
+ 'ns' => 'user-select:none;',
+ ],
+ 'light' => [
+ 'default' => 'background:none; color:#CC7832; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all',
+ 'num' => 'font-weight:bold; color:#1299DA',
+ 'const' => 'font-weight:bold',
+ 'str' => 'font-weight:bold; color:#629755;',
+ 'note' => 'color:#6897BB',
+ 'ref' => 'color:#6E6E6E',
+ 'public' => 'color:#262626',
+ 'protected' => 'color:#262626',
+ 'private' => 'color:#262626',
+ 'meta' => 'color:#B729D9',
+ 'key' => 'color:#789339',
+ 'index' => 'color:#1299DA',
+ 'ellipsis' => 'color:#CC7832',
+ 'ns' => 'user-select:none;',
+ ],
+ ];
+
+ protected $dumpHeader;
+ protected $dumpPrefix = '
';
+ protected $dumpSuffix = ' ';
+ protected $dumpId = 'sf-dump';
+ protected $colors = true;
+ protected $headerIsDumped = false;
+ protected $lastDepth = -1;
+ protected $styles;
+
+ private $displayOptions = [
+ 'maxDepth' => 1,
+ 'maxStringLength' => 160,
+ 'fileLinkFormat' => null,
+ ];
+ private $extraDisplayOptions = [];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct($output = null, string $charset = null, int $flags = 0)
+ {
+ AbstractDumper::__construct($output, $charset, $flags);
+ $this->dumpId = 'sf-dump-'.mt_rand();
+ $this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
+ $this->styles = static::$themes['dark'] ?? self::$themes['dark'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setStyles(array $styles)
+ {
+ $this->headerIsDumped = false;
+ $this->styles = $styles + $this->styles;
+ }
+
+ public function setTheme(string $themeName)
+ {
+ if (!isset(static::$themes[$themeName])) {
+ throw new \InvalidArgumentException(sprintf('Theme "%s" does not exist in class "%s".', $themeName, static::class));
+ }
+
+ $this->setStyles(static::$themes[$themeName]);
+ }
+
+ /**
+ * Configures display options.
+ *
+ * @param array $displayOptions A map of display options to customize the behavior
+ */
+ public function setDisplayOptions(array $displayOptions)
+ {
+ $this->headerIsDumped = false;
+ $this->displayOptions = $displayOptions + $this->displayOptions;
+ }
+
+ /**
+ * Sets an HTML header that will be dumped once in the output stream.
+ *
+ * @param string $header An HTML string
+ */
+ public function setDumpHeader($header)
+ {
+ $this->dumpHeader = $header;
+ }
+
+ /**
+ * Sets an HTML prefix and suffix that will encapse every single dump.
+ *
+ * @param string $prefix The prepended HTML string
+ * @param string $suffix The appended HTML string
+ */
+ public function setDumpBoundaries($prefix, $suffix)
+ {
+ $this->dumpPrefix = $prefix;
+ $this->dumpSuffix = $suffix;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dump(Data $data, $output = null, array $extraDisplayOptions = [])
+ {
+ $this->extraDisplayOptions = $extraDisplayOptions;
+ $result = parent::dump($data, $output);
+ $this->dumpId = 'sf-dump-'.mt_rand();
+
+ return $result;
+ }
+
+ /**
+ * Dumps the HTML header.
+ */
+ protected function getDumpHeader()
+ {
+ $this->headerIsDumped = null !== $this->outputStream ? $this->outputStream : $this->lineDumper;
+
+ if (null !== $this->dumpHeader) {
+ return $this->dumpHeader;
+ }
+
+ $line = str_replace('{$options}', json_encode($this->displayOptions, \JSON_FORCE_OBJECT), <<<'EOHTML'
+'.$this->dumpHeader;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dumpString(Cursor $cursor, $str, $bin, $cut)
+ {
+ if ('' === $str && isset($cursor->attr['img-data'], $cursor->attr['content-type'])) {
+ $this->dumpKey($cursor);
+ $this->line .= $this->style('default', $cursor->attr['img-size'] ?? '', []).' ';
+ $this->endValue($cursor);
+ $this->line .= $this->indentPad;
+ $this->line .= sprintf(' ', $cursor->attr['content-type'], base64_encode($cursor->attr['img-data']));
+ $this->endValue($cursor);
+ } else {
+ parent::dumpString($cursor, $str, $bin, $cut);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function enterHash(Cursor $cursor, $type, $class, $hasChild)
+ {
+ if (Cursor::HASH_OBJECT === $type) {
+ $cursor->attr['depth'] = $cursor->depth;
+ }
+ parent::enterHash($cursor, $type, $class, false);
+
+ if ($cursor->skipChildren) {
+ $cursor->skipChildren = false;
+ $eol = ' class=sf-dump-compact>';
+ } elseif ($this->expandNextHash) {
+ $this->expandNextHash = false;
+ $eol = ' class=sf-dump-expanded>';
+ } else {
+ $eol = '>';
+ }
+
+ if ($hasChild) {
+ $this->line .= 'refIndex) {
+ $r = Cursor::HASH_OBJECT !== $type ? 1 - (Cursor::HASH_RESOURCE !== $type) : 2;
+ $r .= $r && 0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->refIndex;
+
+ $this->line .= sprintf(' id=%s-ref%s', $this->dumpId, $r);
+ }
+ $this->line .= $eol;
+ $this->dumpLine($cursor->depth);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut)
+ {
+ $this->dumpEllipsis($cursor, $hasChild, $cut);
+ if ($hasChild) {
+ $this->line .= ' ';
+ }
+ parent::leaveHash($cursor, $type, $class, $hasChild, 0);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function style($style, $value, $attr = [])
+ {
+ if ('' === $value) {
+ return '';
+ }
+
+ $v = esc($value);
+
+ if ('ref' === $style) {
+ if (empty($attr['count'])) {
+ return sprintf('%s ', $v);
+ }
+ $r = ('#' !== $v[0] ? 1 - ('@' !== $v[0]) : 2).substr($value, 1);
+
+ return sprintf('%s ', $this->dumpId, $r, 1 + $attr['count'], $v);
+ }
+
+ if ('const' === $style && isset($attr['value'])) {
+ $style .= sprintf(' title="%s"', esc(is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value'])));
+ } elseif ('public' === $style) {
+ $style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property');
+ } elseif ('str' === $style && 1 < $attr['length']) {
+ $style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : '');
+ } elseif ('note' === $style && 0 < ($attr['depth'] ?? 0) && false !== $c = strrpos($value, '\\')) {
+ $style .= ' title=""';
+ $attr += [
+ 'ellipsis' => \strlen($value) - $c,
+ 'ellipsis-type' => 'note',
+ 'ellipsis-tail' => 1,
+ ];
+ } elseif ('protected' === $style) {
+ $style .= ' title="Protected property"';
+ } elseif ('meta' === $style && isset($attr['title'])) {
+ $style .= sprintf(' title="%s"', esc($this->utf8Encode($attr['title'])));
+ } elseif ('private' === $style) {
+ $style .= sprintf(' title="Private property defined in class:
`%s`"', esc($this->utf8Encode($attr['class'])));
+ }
+ $map = static::$controlCharsMap;
+
+ if (isset($attr['ellipsis'])) {
+ $class = 'sf-dump-ellipsis';
+ if (isset($attr['ellipsis-type'])) {
+ $class = sprintf('"%s sf-dump-ellipsis-%s"', $class, $attr['ellipsis-type']);
+ }
+ $label = esc(substr($value, -$attr['ellipsis']));
+ $style = str_replace(' title="', " title=\"$v\n", $style);
+ $v = sprintf('%s ', $class, substr($v, 0, -\strlen($label)));
+
+ if (!empty($attr['ellipsis-tail'])) {
+ $tail = \strlen(esc(substr($value, -$attr['ellipsis'], $attr['ellipsis-tail'])));
+ $v .= sprintf('%s %s', $class, substr($label, 0, $tail), substr($label, $tail));
+ } else {
+ $v .= $label;
+ }
+ }
+
+ $v = "".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) {
+ $s = $b = '';
+ }, $v).' ';
+
+ if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) {
+ $attr['href'] = $href;
+ }
+ if (isset($attr['href'])) {
+ $target = isset($attr['file']) ? '' : ' target="_blank"';
+ $v = sprintf('%s ', esc($this->utf8Encode($attr['href'])), $target, $v);
+ }
+ if (isset($attr['lang'])) {
+ $v = sprintf('%s', esc($attr['lang']), $v);
+ }
+
+ return $v;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function dumpLine($depth, $endOfValue = false)
+ {
+ if (-1 === $this->lastDepth) {
+ $this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line;
+ }
+ if ($this->headerIsDumped !== (null !== $this->outputStream ? $this->outputStream : $this->lineDumper)) {
+ $this->line = $this->getDumpHeader().$this->line;
+ }
+
+ if (-1 === $depth) {
+ $args = ['"'.$this->dumpId.'"'];
+ if ($this->extraDisplayOptions) {
+ $args[] = json_encode($this->extraDisplayOptions, \JSON_FORCE_OBJECT);
+ }
+ // Replace is for BC
+ $this->line .= sprintf(str_replace('"%s"', '%s', $this->dumpSuffix), implode(', ', $args));
+ }
+ $this->lastDepth = $depth;
+
+ $this->line = mb_convert_encoding($this->line, 'HTML-ENTITIES', 'UTF-8');
+
+ if (-1 === $depth) {
+ AbstractDumper::dumpLine(0);
+ }
+ AbstractDumper::dumpLine($depth);
+ }
+
+ private function getSourceLink(string $file, int $line)
+ {
+ $options = $this->extraDisplayOptions + $this->displayOptions;
+
+ if ($fmt = $options['fileLinkFormat']) {
+ return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line);
+ }
+
+ return false;
+ }
+}
+
+function esc($str)
+{
+ return htmlspecialchars($str, \ENT_QUOTES, 'UTF-8');
+}
diff --git a/vendor/symfony/var-dumper/Dumper/ServerDumper.php b/vendor/symfony/var-dumper/Dumper/ServerDumper.php
new file mode 100644
index 000000000..94795bf6d
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/ServerDumper.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper;
+
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
+use Symfony\Component\VarDumper\Server\Connection;
+
+/**
+ * ServerDumper forwards serialized Data clones to a server.
+ *
+ * @author Maxime Steinhausser
+ */
+class ServerDumper implements DataDumperInterface
+{
+ private $connection;
+ private $wrappedDumper;
+
+ /**
+ * @param string $host The server host
+ * @param DataDumperInterface|null $wrappedDumper A wrapped instance used whenever we failed contacting the server
+ * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name
+ */
+ public function __construct(string $host, DataDumperInterface $wrappedDumper = null, array $contextProviders = [])
+ {
+ $this->connection = new Connection($host, $contextProviders);
+ $this->wrappedDumper = $wrappedDumper;
+ }
+
+ public function getContextProviders(): array
+ {
+ return $this->connection->getContextProviders();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dump(Data $data)
+ {
+ if (!$this->connection->write($data) && $this->wrappedDumper) {
+ $this->wrappedDumper->dump($data);
+ }
+ }
+}
diff --git a/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php b/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php
new file mode 100644
index 000000000..122f0d358
--- /dev/null
+++ b/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Exception;
+
+/**
+ * @author Nicolas Grekas
+ */
+class ThrowingCasterException extends \Exception
+{
+ /**
+ * @param \Throwable $prev The exception thrown from the caster
+ */
+ public function __construct(\Throwable $prev)
+ {
+ parent::__construct('Unexpected '.\get_class($prev).' thrown from a caster: '.$prev->getMessage(), 0, $prev);
+ }
+}
diff --git a/vendor/symfony/var-dumper/LICENSE b/vendor/symfony/var-dumper/LICENSE
new file mode 100644
index 000000000..c1f0aac1c
--- /dev/null
+++ b/vendor/symfony/var-dumper/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014-2021 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/var-dumper/README.md b/vendor/symfony/var-dumper/README.md
new file mode 100644
index 000000000..bdac24477
--- /dev/null
+++ b/vendor/symfony/var-dumper/README.md
@@ -0,0 +1,15 @@
+VarDumper Component
+===================
+
+The VarDumper component provides mechanisms for walking through any arbitrary
+PHP variable. It provides a better `dump()` function that you can use instead
+of `var_dump`.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/var_dumper/introduction.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/vendor/symfony/var-dumper/Resources/bin/var-dump-server b/vendor/symfony/var-dumper/Resources/bin/var-dump-server
new file mode 100755
index 000000000..98c813a06
--- /dev/null
+++ b/vendor/symfony/var-dumper/Resources/bin/var-dump-server
@@ -0,0 +1,63 @@
+#!/usr/bin/env php
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Starts a dump server to collect and output dumps on a single place with multiple formats support.
+ *
+ * @author Maxime Steinhausser
+ */
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Input\ArgvInput;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Logger\ConsoleLogger;
+use Symfony\Component\Console\Output\ConsoleOutput;
+use Symfony\Component\VarDumper\Command\ServerDumpCommand;
+use Symfony\Component\VarDumper\Server\DumpServer;
+
+function includeIfExists(string $file): bool
+{
+ return file_exists($file) && include $file;
+}
+
+if (
+ !includeIfExists(__DIR__ . '/../../../../autoload.php') &&
+ !includeIfExists(__DIR__ . '/../../vendor/autoload.php') &&
+ !includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php')
+) {
+ fwrite(STDERR, 'Install dependencies using Composer.'.PHP_EOL);
+ exit(1);
+}
+
+if (!class_exists(Application::class)) {
+ fwrite(STDERR, 'You need the "symfony/console" component in order to run the VarDumper server.'.PHP_EOL);
+ exit(1);
+}
+
+$input = new ArgvInput();
+$output = new ConsoleOutput();
+$defaultHost = '127.0.0.1:9912';
+$host = $input->getParameterOption(['--host'], $_SERVER['VAR_DUMPER_SERVER'] ?? $defaultHost, true);
+$logger = interface_exists(LoggerInterface::class) ? new ConsoleLogger($output->getErrorOutput()) : null;
+
+$app = new Application();
+
+$app->getDefinition()->addOption(
+ new InputOption('--host', null, InputOption::VALUE_REQUIRED, 'The address the server should listen to', $defaultHost)
+);
+
+$app->add($command = new ServerDumpCommand(new DumpServer($host, $logger)))
+ ->getApplication()
+ ->setDefaultCommand($command->getName(), true)
+ ->run($input, $output)
+;
diff --git a/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css b/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css
new file mode 100644
index 000000000..8f706d640
--- /dev/null
+++ b/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css
@@ -0,0 +1,130 @@
+body {
+ display: flex;
+ flex-direction: column-reverse;
+ justify-content: flex-end;
+ max-width: 1140px;
+ margin: auto;
+ padding: 15px;
+ word-wrap: break-word;
+ background-color: #F9F9F9;
+ color: #222;
+ font-family: Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ line-height: 1.4;
+}
+p {
+ margin: 0;
+}
+a {
+ color: #218BC3;
+ text-decoration: none;
+}
+a:hover {
+ text-decoration: underline;
+}
+.text-small {
+ font-size: 12px !important;
+}
+article {
+ margin: 5px;
+ margin-bottom: 10px;
+}
+article > header > .row {
+ display: flex;
+ flex-direction: row;
+ align-items: baseline;
+ margin-bottom: 10px;
+}
+article > header > .row > .col {
+ flex: 1;
+ display: flex;
+ align-items: baseline;
+}
+article > header > .row > h2 {
+ font-size: 14px;
+ color: #222;
+ font-weight: normal;
+ font-family: "Lucida Console", monospace, sans-serif;
+ word-break: break-all;
+ margin: 20px 5px 0 0;
+ user-select: all;
+}
+article > header > .row > h2 > code {
+ white-space: nowrap;
+ user-select: none;
+ color: #cc2255;
+ background-color: #f7f7f9;
+ border: 1px solid #e1e1e8;
+ border-radius: 3px;
+ margin-right: 5px;
+ padding: 0 3px;
+}
+article > header > .row > time.col {
+ flex: 0;
+ text-align: right;
+ white-space: nowrap;
+ color: #999;
+ font-style: italic;
+}
+article > header ul.tags {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ font-size: 12px;
+}
+article > header ul.tags > li {
+ user-select: all;
+ margin-bottom: 2px;
+}
+article > header ul.tags > li > span.badge {
+ display: inline-block;
+ padding: .25em .4em;
+ margin-right: 5px;
+ border-radius: 4px;
+ background-color: #6c757d3b;
+ color: #524d4d;
+ font-size: 12px;
+ text-align: center;
+ font-weight: 700;
+ line-height: 1;
+ white-space: nowrap;
+ vertical-align: baseline;
+ user-select: none;
+}
+article > section.body {
+ border: 1px solid #d8d8d8;
+ background: #FFF;
+ padding: 10px;
+ border-radius: 3px;
+}
+pre.sf-dump {
+ border-radius: 3px;
+ margin-bottom: 0;
+}
+.hidden {
+ display: none !important;
+}
+.dumped-tag > .sf-dump {
+ display: inline-block;
+ margin: 0;
+ padding: 1px 5px;
+ line-height: 1.4;
+ vertical-align: top;
+ background-color: transparent;
+ user-select: auto;
+}
+.dumped-tag > pre.sf-dump,
+.dumped-tag > .sf-dump-default {
+ color: #CC7832;
+ background: none;
+}
+.dumped-tag > .sf-dump .sf-dump-str { color: #629755; }
+.dumped-tag > .sf-dump .sf-dump-private,
+.dumped-tag > .sf-dump .sf-dump-protected,
+.dumped-tag > .sf-dump .sf-dump-public { color: #262626; }
+.dumped-tag > .sf-dump .sf-dump-note { color: #6897BB; }
+.dumped-tag > .sf-dump .sf-dump-key { color: #789339; }
+.dumped-tag > .sf-dump .sf-dump-ref { color: #6E6E6E; }
+.dumped-tag > .sf-dump .sf-dump-ellipsis { color: #CC7832; max-width: 100em; }
+.dumped-tag > .sf-dump .sf-dump-ellipsis-path { max-width: 5em; }
+.dumped-tag > .sf-dump .sf-dump-ns { user-select: none; }
diff --git a/vendor/symfony/var-dumper/Resources/functions/dump.php b/vendor/symfony/var-dumper/Resources/functions/dump.php
new file mode 100644
index 000000000..a485d573a
--- /dev/null
+++ b/vendor/symfony/var-dumper/Resources/functions/dump.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Component\VarDumper\VarDumper;
+
+if (!function_exists('dump')) {
+ /**
+ * @author Nicolas Grekas
+ */
+ function dump($var, ...$moreVars)
+ {
+ VarDumper::dump($var);
+
+ foreach ($moreVars as $v) {
+ VarDumper::dump($v);
+ }
+
+ if (1 < func_num_args()) {
+ return func_get_args();
+ }
+
+ return $var;
+ }
+}
+
+if (!function_exists('dd')) {
+ function dd(...$vars)
+ {
+ foreach ($vars as $v) {
+ VarDumper::dump($v);
+ }
+
+ exit(1);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js b/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js
new file mode 100644
index 000000000..63101e57c
--- /dev/null
+++ b/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js
@@ -0,0 +1,10 @@
+document.addEventListener('DOMContentLoaded', function() {
+ let prev = null;
+ Array.from(document.getElementsByTagName('article')).reverse().forEach(function (article) {
+ const dedupId = article.dataset.dedupId;
+ if (dedupId === prev) {
+ article.getElementsByTagName('header')[0].classList.add('hidden');
+ }
+ prev = dedupId;
+ });
+});
diff --git a/vendor/symfony/var-dumper/Server/Connection.php b/vendor/symfony/var-dumper/Server/Connection.php
new file mode 100644
index 000000000..d8be23587
--- /dev/null
+++ b/vendor/symfony/var-dumper/Server/Connection.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Server;
+
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
+
+/**
+ * Forwards serialized Data clones to a server.
+ *
+ * @author Maxime Steinhausser
+ */
+class Connection
+{
+ private $host;
+ private $contextProviders;
+ private $socket;
+
+ /**
+ * @param string $host The server host
+ * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name
+ */
+ public function __construct(string $host, array $contextProviders = [])
+ {
+ if (false === strpos($host, '://')) {
+ $host = 'tcp://'.$host;
+ }
+
+ $this->host = $host;
+ $this->contextProviders = $contextProviders;
+ }
+
+ public function getContextProviders(): array
+ {
+ return $this->contextProviders;
+ }
+
+ public function write(Data $data): bool
+ {
+ $socketIsFresh = !$this->socket;
+ if (!$this->socket = $this->socket ?: $this->createSocket()) {
+ return false;
+ }
+
+ $context = ['timestamp' => microtime(true)];
+ foreach ($this->contextProviders as $name => $provider) {
+ $context[$name] = $provider->getContext();
+ }
+ $context = array_filter($context);
+ $encodedPayload = base64_encode(serialize([$data, $context]))."\n";
+
+ set_error_handler([self::class, 'nullErrorHandler']);
+ try {
+ if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) {
+ return true;
+ }
+ if (!$socketIsFresh) {
+ stream_socket_shutdown($this->socket, \STREAM_SHUT_RDWR);
+ fclose($this->socket);
+ $this->socket = $this->createSocket();
+ }
+ if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) {
+ return true;
+ }
+ } finally {
+ restore_error_handler();
+ }
+
+ return false;
+ }
+
+ private static function nullErrorHandler($t, $m)
+ {
+ // no-op
+ }
+
+ private function createSocket()
+ {
+ set_error_handler([self::class, 'nullErrorHandler']);
+ try {
+ return stream_socket_client($this->host, $errno, $errstr, 3, \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT);
+ } finally {
+ restore_error_handler();
+ }
+ }
+}
diff --git a/vendor/symfony/var-dumper/Server/DumpServer.php b/vendor/symfony/var-dumper/Server/DumpServer.php
new file mode 100644
index 000000000..55510c0e2
--- /dev/null
+++ b/vendor/symfony/var-dumper/Server/DumpServer.php
@@ -0,0 +1,107 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Server;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * A server collecting Data clones sent by a ServerDumper.
+ *
+ * @author Maxime Steinhausser
+ *
+ * @final
+ */
+class DumpServer
+{
+ private $host;
+ private $socket;
+ private $logger;
+
+ public function __construct(string $host, LoggerInterface $logger = null)
+ {
+ if (false === strpos($host, '://')) {
+ $host = 'tcp://'.$host;
+ }
+
+ $this->host = $host;
+ $this->logger = $logger;
+ }
+
+ public function start(): void
+ {
+ if (!$this->socket = stream_socket_server($this->host, $errno, $errstr)) {
+ throw new \RuntimeException(sprintf('Server start failed on "%s": ', $this->host).$errstr.' '.$errno);
+ }
+ }
+
+ public function listen(callable $callback): void
+ {
+ if (null === $this->socket) {
+ $this->start();
+ }
+
+ foreach ($this->getMessages() as $clientId => $message) {
+ $payload = @unserialize(base64_decode($message), ['allowed_classes' => [Data::class, Stub::class]]);
+
+ // Impossible to decode the message, give up.
+ if (false === $payload) {
+ if ($this->logger) {
+ $this->logger->warning('Unable to decode a message from {clientId} client.', ['clientId' => $clientId]);
+ }
+
+ continue;
+ }
+
+ if (!\is_array($payload) || \count($payload) < 2 || !$payload[0] instanceof Data || !\is_array($payload[1])) {
+ if ($this->logger) {
+ $this->logger->warning('Invalid payload from {clientId} client. Expected an array of two elements (Data $data, array $context)', ['clientId' => $clientId]);
+ }
+
+ continue;
+ }
+
+ [$data, $context] = $payload;
+
+ $callback($data, $context, $clientId);
+ }
+ }
+
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ private function getMessages(): iterable
+ {
+ $sockets = [(int) $this->socket => $this->socket];
+ $write = [];
+
+ while (true) {
+ $read = $sockets;
+ stream_select($read, $write, $write, null);
+
+ foreach ($read as $stream) {
+ if ($this->socket === $stream) {
+ $stream = stream_socket_accept($this->socket);
+ $sockets[(int) $stream] = $stream;
+ } elseif (feof($stream)) {
+ unset($sockets[(int) $stream]);
+ fclose($stream);
+ } else {
+ yield (int) $stream => fgets($stream);
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php b/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php
new file mode 100644
index 000000000..3d3d18eeb
--- /dev/null
+++ b/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php
@@ -0,0 +1,87 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Test;
+
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+/**
+ * @author Nicolas Grekas
+ */
+trait VarDumperTestTrait
+{
+ /**
+ * @internal
+ */
+ private $varDumperConfig = [
+ 'casters' => [],
+ 'flags' => null,
+ ];
+
+ protected function setUpVarDumper(array $casters, int $flags = null): void
+ {
+ $this->varDumperConfig['casters'] = $casters;
+ $this->varDumperConfig['flags'] = $flags;
+ }
+
+ /**
+ * @after
+ */
+ protected function tearDownVarDumper(): void
+ {
+ $this->varDumperConfig['casters'] = [];
+ $this->varDumperConfig['flags'] = null;
+ }
+
+ public function assertDumpEquals($expected, $data, $filter = 0, $message = '')
+ {
+ $this->assertSame($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message);
+ }
+
+ public function assertDumpMatchesFormat($expected, $data, $filter = 0, $message = '')
+ {
+ $this->assertStringMatchesFormat($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message);
+ }
+
+ /**
+ * @return string|null
+ */
+ protected function getDump($data, $key = null, $filter = 0)
+ {
+ if (null === $flags = $this->varDumperConfig['flags']) {
+ $flags = getenv('DUMP_LIGHT_ARRAY') ? CliDumper::DUMP_LIGHT_ARRAY : 0;
+ $flags |= getenv('DUMP_STRING_LENGTH') ? CliDumper::DUMP_STRING_LENGTH : 0;
+ $flags |= getenv('DUMP_COMMA_SEPARATOR') ? CliDumper::DUMP_COMMA_SEPARATOR : 0;
+ }
+
+ $cloner = new VarCloner();
+ $cloner->addCasters($this->varDumperConfig['casters']);
+ $cloner->setMaxItems(-1);
+ $dumper = new CliDumper(null, null, $flags);
+ $dumper->setColors(false);
+ $data = $cloner->cloneVar($data, $filter)->withRefHandles(false);
+ if (null !== $key && null === $data = $data->seek($key)) {
+ return null;
+ }
+
+ return rtrim($dumper->dump($data, true));
+ }
+
+ private function prepareExpectation($expected, int $filter): string
+ {
+ if (!\is_string($expected)) {
+ $expected = $this->getDump($expected, null, $filter);
+ }
+
+ return rtrim($expected);
+ }
+}
diff --git a/vendor/symfony/var-dumper/VarDumper.php b/vendor/symfony/var-dumper/VarDumper.php
new file mode 100644
index 000000000..febc1e0d1
--- /dev/null
+++ b/vendor/symfony/var-dumper/VarDumper.php
@@ -0,0 +1,66 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper;
+
+use Symfony\Component\VarDumper\Caster\ReflectionCaster;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
+use Symfony\Component\VarDumper\Dumper\ContextualizedDumper;
+use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+
+// Load the global dump() function
+require_once __DIR__.'/Resources/functions/dump.php';
+
+/**
+ * @author Nicolas Grekas
+ */
+class VarDumper
+{
+ private static $handler;
+
+ public static function dump($var)
+ {
+ if (null === self::$handler) {
+ $cloner = new VarCloner();
+ $cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);
+
+ if (isset($_SERVER['VAR_DUMPER_FORMAT'])) {
+ $dumper = 'html' === $_SERVER['VAR_DUMPER_FORMAT'] ? new HtmlDumper() : new CliDumper();
+ } else {
+ $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg']) ? new CliDumper() : new HtmlDumper();
+ }
+
+ $dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]);
+
+ self::$handler = function ($var) use ($cloner, $dumper) {
+ $dumper->dump($cloner->cloneVar($var));
+ };
+ }
+
+ return (self::$handler)($var);
+ }
+
+ public static function setHandler(callable $callable = null)
+ {
+ $prevHandler = self::$handler;
+
+ // Prevent replacing the handler with expected format as soon as the env var was set:
+ if (isset($_SERVER['VAR_DUMPER_FORMAT'])) {
+ return $prevHandler;
+ }
+
+ self::$handler = $callable;
+
+ return $prevHandler;
+ }
+}
diff --git a/vendor/symfony/var-dumper/composer.json b/vendor/symfony/var-dumper/composer.json
new file mode 100644
index 000000000..642ee62b0
--- /dev/null
+++ b/vendor/symfony/var-dumper/composer.json
@@ -0,0 +1,50 @@
+{
+ "name": "symfony/var-dumper",
+ "type": "library",
+ "description": "Provides mechanisms for walking through any arbitrary PHP variable",
+ "keywords": ["dump", "debug"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.1.3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/polyfill-php72": "~1.5",
+ "symfony/polyfill-php80": "^1.15"
+ },
+ "require-dev": {
+ "ext-iconv": "*",
+ "symfony/console": "^3.4|^4.0|^5.0",
+ "symfony/process": "^4.4|^5.0",
+ "twig/twig": "^1.43|^2.13|^3.0.4"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
+ "symfony/console": "<3.4"
+ },
+ "suggest": {
+ "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
+ "ext-intl": "To show region name in time zone dump",
+ "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
+ },
+ "autoload": {
+ "files": [ "Resources/functions/dump.php" ],
+ "psr-4": { "Symfony\\Component\\VarDumper\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "bin": [
+ "Resources/bin/var-dump-server"
+ ],
+ "minimum-stability": "dev"
+}
diff --git a/thinkphp/.gitignore b/vendor/topthink/framework/.gitignore
old mode 100755
new mode 100644
similarity index 84%
rename from thinkphp/.gitignore
rename to vendor/topthink/framework/.gitignore
index f7775ba41..b267fbae4
--- a/thinkphp/.gitignore
+++ b/vendor/topthink/framework/.gitignore
@@ -3,6 +3,5 @@ composer.phar
composer.lock
.DS_Store
Thumbs.db
-/phpunit.xml
/.idea
/.vscode
\ No newline at end of file
diff --git a/vendor/topthink/framework/.travis.yml b/vendor/topthink/framework/.travis.yml
new file mode 100644
index 000000000..28987af82
--- /dev/null
+++ b/vendor/topthink/framework/.travis.yml
@@ -0,0 +1,35 @@
+dist: xenial
+language: php
+
+matrix:
+ fast_finish: true
+ include:
+ - php: 7.1
+ - php: 7.2
+ - php: 7.3
+
+cache:
+ directories:
+ - $HOME/.composer/cache
+
+services:
+ - memcached
+ - redis-server
+ - mysql
+
+before_install:
+ - echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
+ - echo 'xdebug.mode = coverage' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
+ - printf "\n" | pecl install -f redis
+ - travis_retry composer self-update
+ - mysql -e 'CREATE DATABASE test;'
+
+install:
+ - travis_retry composer update --prefer-dist --no-interaction --prefer-stable --no-suggest
+
+script:
+ - vendor/bin/phpunit --coverage-clover build/logs/coverage.xml
+
+after_script:
+ - travis_retry wget https://scrutinizer-ci.com/ocular.phar
+ - php ocular.phar code-coverage:upload --format=php-clover build/logs/coverage.xml
diff --git a/thinkphp/CONTRIBUTING.md b/vendor/topthink/framework/CONTRIBUTING.md
old mode 100755
new mode 100644
similarity index 95%
rename from thinkphp/CONTRIBUTING.md
rename to vendor/topthink/framework/CONTRIBUTING.md
index 6cefcb38c..efa3ad972
--- a/thinkphp/CONTRIBUTING.md
+++ b/vendor/topthink/framework/CONTRIBUTING.md
@@ -22,7 +22,7 @@ ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡
* 本项目代码格式化标准选用 [**PSR-2**](http://www.kancloud.cn/thinkphp/php-fig-psr/3141);
* 类名和类文件名遵循 [**PSR-4**](http://www.kancloud.cn/thinkphp/php-fig-psr/3144);
* 对于 Issues 的处理,请使用诸如 `fix #xxx(Issue ID)` 的 commit title 直接关闭 issue。
-* 系统会自动在 PHP 5.4 5.5 5.6 7.0 和 HHVM 上测试修改,其中 HHVM 下的测试容许报错,请确保你的修改符合 PHP 5.4 ~ 5.6 和 PHP 7.0 的语法规范;
+* 系统会自动在 PHP 7.1 ~ 7.3 上测试修改,请确保你的修改符合 PHP 7.1 ~ 7.3 的语法规范;
* 管理员不会合并造成 CI faild 的修改,若出现 CI faild 请检查自己的源代码或修改相应的[单元测试文件](tests);
## GitHub Issue
@@ -84,7 +84,7 @@ GitHub 提供了 Issue 功能,该功能可以用于:
或自行安装
- Apache / Nginx
-- PHP 5.4 ~ 5.6
+- PHP 7.1 ~ 7.3
- MySQL / MariaDB
*Windows 用户推荐添加 PHP bin 目录到 PATH,方便使用 composer*
diff --git a/thinkphp/LICENSE.txt b/vendor/topthink/framework/LICENSE.txt
old mode 100755
new mode 100644
similarity index 96%
rename from thinkphp/LICENSE.txt
rename to vendor/topthink/framework/LICENSE.txt
index 774fa76fd..4e910bb20
--- a/thinkphp/LICENSE.txt
+++ b/vendor/topthink/framework/LICENSE.txt
@@ -1,6 +1,6 @@
ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
-版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn)
+版权所有Copyright © 2006-2019 by ThinkPHP (http://thinkphp.cn)
All rights reserved。
ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
diff --git a/vendor/topthink/framework/README.md b/vendor/topthink/framework/README.md
new file mode 100644
index 000000000..0ea03c987
--- /dev/null
+++ b/vendor/topthink/framework/README.md
@@ -0,0 +1,86 @@
+
+
+ThinkPHP 6.0
+===============
+
+[](https://travis-ci.org/top-think/framework)
+[](https://scrutinizer-ci.com/g/top-think/framework/?branch=6.0)
+[](https://scrutinizer-ci.com/g/top-think/framework/?branch=6.0)
+[](https://packagist.org/packages/topthink/framework)
+[](https://packagist.org/packages/topthink/framework)
+[](http://www.php.net/)
+[](https://packagist.org/packages/topthink/framework)
+
+ThinkPHP6.0底层架构采用PHP7.1改写和进一步优化。
+
+[官方应用服务市场](https://market.topthink.com) | [`ThinkAPI`——官方统一API服务](https://docs.topthink.com/think-api/)
+
+## 主要新特性
+
+* 采用`PHP7`强类型(严格模式)
+* 支持更多的`PSR`规范
+* 原生多应用支持
+* 系统服务注入支持
+* ORM作为独立组件使用
+* 增加Filesystem
+* 全新的事件系统
+* 模板引擎分离出核心
+* 内部功能中间件化
+* SESSION机制改进
+* 日志多通道支持
+* 规范扩展接口
+* 更强大的控制台
+* 对Swoole以及协程支持改进
+* 对IDE更加友好
+* 统一和精简大量用法
+
+
+> ThinkPHP6.0的运行环境要求PHP7.1+,兼容PHP8.0。
+
+## 安装
+
+~~~
+composer create-project topthink/think tp
+~~~
+
+启动服务
+
+~~~
+cd tp
+php think run
+~~~
+
+然后就可以在浏览器中访问
+
+~~~
+http://localhost:8000
+~~~
+
+如果需要更新框架使用
+~~~
+composer update topthink/framework
+~~~
+
+## 文档
+
+[完全开发手册](https://www.kancloud.cn/manual/thinkphp6_0/content)
+
+## 命名规范
+
+`ThinkPHP6`遵循PSR-2命名规范和PSR-4自动加载规范。
+
+## 参与开发
+
+直接提交PR或者Issue即可
+
+## 版权信息
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+
+本项目包含的第三方源码和二进制文件之版权信息另行标注。
+
+版权所有Copyright © 2006-2021 by ThinkPHP (http://thinkphp.cn) All rights reserved。
+
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+更多细节参阅 [LICENSE.txt](LICENSE.txt)
diff --git a/vendor/topthink/framework/composer.json b/vendor/topthink/framework/composer.json
new file mode 100644
index 000000000..9afc513a6
--- /dev/null
+++ b/vendor/topthink/framework/composer.json
@@ -0,0 +1,54 @@
+{
+ "name": "topthink/framework",
+ "description": "The ThinkPHP Framework.",
+ "keywords": [
+ "framework",
+ "thinkphp",
+ "ORM"
+ ],
+ "homepage": "http://thinkphp.cn/",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "liu21st",
+ "email": "liu21st@gmail.com"
+ },
+ {
+ "name": "yunwuxin",
+ "email": "448901948@qq.com"
+ }
+ ],
+ "require": {
+ "php": ">=7.1.0",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "league/flysystem": "^1.0",
+ "league/flysystem-cached-adapter": "^1.0",
+ "psr/log": "~1.0",
+ "psr/container": "~1.0",
+ "psr/simple-cache": "^1.0",
+ "topthink/think-orm": "^2.0",
+ "topthink/think-helper": "^3.1.1"
+ },
+ "require-dev": {
+ "mikey179/vfsstream": "^1.6",
+ "mockery/mockery": "^1.2",
+ "phpunit/phpunit": "^7.0"
+ },
+ "autoload": {
+ "files": [],
+ "psr-4": {
+ "think\\": "src/think/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "think\\tests\\": "tests/"
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "config": {
+ "sort-packages": true
+ }
+}
diff --git a/thinkphp/logo.png b/vendor/topthink/framework/logo.png
old mode 100755
new mode 100644
similarity index 100%
rename from thinkphp/logo.png
rename to vendor/topthink/framework/logo.png
diff --git a/vendor/topthink/framework/phpunit.xml.dist b/vendor/topthink/framework/phpunit.xml.dist
new file mode 100644
index 000000000..e20a13381
--- /dev/null
+++ b/vendor/topthink/framework/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+ ./tests
+
+
+
+
+ ./src/think
+
+
+
diff --git a/vendor/topthink/framework/src/helper.php b/vendor/topthink/framework/src/helper.php
new file mode 100644
index 000000000..650edcb9f
--- /dev/null
+++ b/vendor/topthink/framework/src/helper.php
@@ -0,0 +1,663 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+//------------------------
+// ThinkPHP 助手函数
+//-------------------------
+
+use think\App;
+use think\Container;
+use think\exception\HttpException;
+use think\exception\HttpResponseException;
+use think\facade\Cache;
+use think\facade\Config;
+use think\facade\Cookie;
+use think\facade\Env;
+use think\facade\Event;
+use think\facade\Lang;
+use think\facade\Log;
+use think\facade\Request;
+use think\facade\Route;
+use think\facade\Session;
+use think\Response;
+use think\response\File;
+use think\response\Json;
+use think\response\Jsonp;
+use think\response\Redirect;
+use think\response\View;
+use think\response\Xml;
+use think\route\Url as UrlBuild;
+use think\Validate;
+
+if (!function_exists('abort')) {
+ /**
+ * 抛出HTTP异常
+ * @param integer|Response $code 状态码 或者 Response对象实例
+ * @param string $message 错误信息
+ * @param array $header 参数
+ */
+ function abort($code, string $message = '', array $header = [])
+ {
+ if ($code instanceof Response) {
+ throw new HttpResponseException($code);
+ } else {
+ throw new HttpException($code, $message, null, $header);
+ }
+ }
+}
+
+if (!function_exists('app')) {
+ /**
+ * 快速获取容器中的实例 支持依赖注入
+ * @param string $name 类名或标识 默认获取当前应用实例
+ * @param array $args 参数
+ * @param bool $newInstance 是否每次创建新的实例
+ * @return object|App
+ */
+ function app(string $name = '', array $args = [], bool $newInstance = false)
+ {
+ return Container::getInstance()->make($name ?: App::class, $args, $newInstance);
+ }
+}
+
+if (!function_exists('bind')) {
+ /**
+ * 绑定一个类到容器
+ * @param string|array $abstract 类标识、接口(支持批量绑定)
+ * @param mixed $concrete 要绑定的类、闭包或者实例
+ * @return Container
+ */
+ function bind($abstract, $concrete = null)
+ {
+ return Container::getInstance()->bind($abstract, $concrete);
+ }
+}
+
+if (!function_exists('cache')) {
+ /**
+ * 缓存管理
+ * @param string $name 缓存名称
+ * @param mixed $value 缓存值
+ * @param mixed $options 缓存参数
+ * @param string $tag 缓存标签
+ * @return mixed
+ */
+ function cache(string $name = null, $value = '', $options = null, $tag = null)
+ {
+ if (is_null($name)) {
+ return app('cache');
+ }
+
+ if ('' === $value) {
+ // 获取缓存
+ return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name);
+ } elseif (is_null($value)) {
+ // 删除缓存
+ return Cache::delete($name);
+ }
+
+ // 缓存数据
+ if (is_array($options)) {
+ $expire = $options['expire'] ?? null; //修复查询缓存无法设置过期时间
+ } else {
+ $expire = $options;
+ }
+
+ if (is_null($tag)) {
+ return Cache::set($name, $value, $expire);
+ } else {
+ return Cache::tag($tag)->set($name, $value, $expire);
+ }
+ }
+}
+
+if (!function_exists('config')) {
+ /**
+ * 获取和设置配置参数
+ * @param string|array $name 参数名
+ * @param mixed $value 参数值
+ * @return mixed
+ */
+ function config($name = '', $value = null)
+ {
+ if (is_array($name)) {
+ return Config::set($name, $value);
+ }
+
+ return 0 === strpos($name, '?') ? Config::has(substr($name, 1)) : Config::get($name, $value);
+ }
+}
+
+if (!function_exists('cookie')) {
+ /**
+ * Cookie管理
+ * @param string $name cookie名称
+ * @param mixed $value cookie值
+ * @param mixed $option 参数
+ * @return mixed
+ */
+ function cookie(string $name, $value = '', $option = null)
+ {
+ if (is_null($value)) {
+ // 删除
+ Cookie::delete($name);
+ } elseif ('' === $value) {
+ // 获取
+ return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1)) : Cookie::get($name);
+ } else {
+ // 设置
+ return Cookie::set($name, $value, $option);
+ }
+ }
+}
+
+if (!function_exists('download')) {
+ /**
+ * 获取\think\response\Download对象实例
+ * @param string $filename 要下载的文件
+ * @param string $name 显示文件名
+ * @param bool $content 是否为内容
+ * @param int $expire 有效期(秒)
+ * @return \think\response\File
+ */
+ function download(string $filename, string $name = '', bool $content = false, int $expire = 180): File
+ {
+ return Response::create($filename, 'file')->name($name)->isContent($content)->expire($expire);
+ }
+}
+
+if (!function_exists('dump')) {
+ /**
+ * 浏览器友好的变量输出
+ * @param mixed $vars 要输出的变量
+ * @return void
+ */
+ function dump(...$vars)
+ {
+ ob_start();
+ var_dump(...$vars);
+
+ $output = ob_get_clean();
+ $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output);
+
+ if (PHP_SAPI == 'cli') {
+ $output = PHP_EOL . $output . PHP_EOL;
+ } else {
+ if (!extension_loaded('xdebug')) {
+ $output = htmlspecialchars($output, ENT_SUBSTITUTE);
+ }
+ $output = '
' . $output . ' ';
+ }
+
+ echo $output;
+ }
+}
+
+if (!function_exists('env')) {
+ /**
+ * 获取环境变量值
+ * @access public
+ * @param string $name 环境变量名(支持二级 .号分割)
+ * @param string $default 默认值
+ * @return mixed
+ */
+ function env(string $name = null, $default = null)
+ {
+ return Env::get($name, $default);
+ }
+}
+
+if (!function_exists('event')) {
+ /**
+ * 触发事件
+ * @param mixed $event 事件名(或者类名)
+ * @param mixed $args 参数
+ * @return mixed
+ */
+ function event($event, $args = null)
+ {
+ return Event::trigger($event, $args);
+ }
+}
+
+if (!function_exists('halt')) {
+ /**
+ * 调试变量并且中断输出
+ * @param mixed $vars 调试变量或者信息
+ */
+ function halt(...$vars)
+ {
+ dump(...$vars);
+
+ throw new HttpResponseException(Response::create());
+ }
+}
+
+if (!function_exists('input')) {
+ /**
+ * 获取输入数据 支持默认值和过滤
+ * @param string $key 获取的变量名
+ * @param mixed $default 默认值
+ * @param string $filter 过滤方法
+ * @return mixed
+ */
+ function input(string $key = '', $default = null, $filter = '')
+ {
+ if (0 === strpos($key, '?')) {
+ $key = substr($key, 1);
+ $has = true;
+ }
+
+ if ($pos = strpos($key, '.')) {
+ // 指定参数来源
+ $method = substr($key, 0, $pos);
+ if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) {
+ $key = substr($key, $pos + 1);
+ if ('server' == $method && is_null($default)) {
+ $default = '';
+ }
+ } else {
+ $method = 'param';
+ }
+ } else {
+ // 默认为自动判断
+ $method = 'param';
+ }
+
+ return isset($has) ?
+ request()->has($key, $method) :
+ request()->$method($key, $default, $filter);
+ }
+}
+
+if (!function_exists('invoke')) {
+ /**
+ * 调用反射实例化对象或者执行方法 支持依赖注入
+ * @param mixed $call 类名或者callable
+ * @param array $args 参数
+ * @return mixed
+ */
+ function invoke($call, array $args = [])
+ {
+ if (is_callable($call)) {
+ return Container::getInstance()->invoke($call, $args);
+ }
+
+ return Container::getInstance()->invokeClass($call, $args);
+ }
+}
+
+if (!function_exists('json')) {
+ /**
+ * 获取\think\response\Json对象实例
+ * @param mixed $data 返回的数据
+ * @param int $code 状态码
+ * @param array $header 头部
+ * @param array $options 参数
+ * @return \think\response\Json
+ */
+ function json($data = [], $code = 200, $header = [], $options = []): Json
+ {
+ return Response::create($data, 'json', $code)->header($header)->options($options);
+ }
+}
+
+if (!function_exists('jsonp')) {
+ /**
+ * 获取\think\response\Jsonp对象实例
+ * @param mixed $data 返回的数据
+ * @param int $code 状态码
+ * @param array $header 头部
+ * @param array $options 参数
+ * @return \think\response\Jsonp
+ */
+ function jsonp($data = [], $code = 200, $header = [], $options = []): Jsonp
+ {
+ return Response::create($data, 'jsonp', $code)->header($header)->options($options);
+ }
+}
+
+if (!function_exists('lang')) {
+ /**
+ * 获取语言变量值
+ * @param string $name 语言变量名
+ * @param array $vars 动态变量值
+ * @param string $lang 语言
+ * @return mixed
+ */
+ function lang(string $name, array $vars = [], string $lang = '')
+ {
+ return Lang::get($name, $vars, $lang);
+ }
+}
+
+if (!function_exists('parse_name')) {
+ /**
+ * 字符串命名风格转换
+ * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
+ * @param string $name 字符串
+ * @param int $type 转换类型
+ * @param bool $ucfirst 首字母是否大写(驼峰规则)
+ * @return string
+ */
+ function parse_name(string $name, int $type = 0, bool $ucfirst = true): string
+ {
+ if ($type) {
+ $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
+ return strtoupper($match[1]);
+ }, $name);
+
+ return $ucfirst ? ucfirst($name) : lcfirst($name);
+ }
+
+ return strtolower(trim(preg_replace('/[A-Z]/', '_\\0', $name), '_'));
+ }
+}
+
+if (!function_exists('redirect')) {
+ /**
+ * 获取\think\response\Redirect对象实例
+ * @param string $url 重定向地址
+ * @param int $code 状态码
+ * @return \think\response\Redirect
+ */
+ function redirect(string $url = '', int $code = 302): Redirect
+ {
+ return Response::create($url, 'redirect', $code);
+ }
+}
+
+if (!function_exists('request')) {
+ /**
+ * 获取当前Request对象实例
+ * @return Request
+ */
+ function request(): \think\Request
+ {
+ return app('request');
+ }
+}
+
+if (!function_exists('response')) {
+ /**
+ * 创建普通 Response 对象实例
+ * @param mixed $data 输出数据
+ * @param int|string $code 状态码
+ * @param array $header 头信息
+ * @param string $type
+ * @return Response
+ */
+ function response($data = '', $code = 200, $header = [], $type = 'html'): Response
+ {
+ return Response::create($data, $type, $code)->header($header);
+ }
+}
+
+if (!function_exists('session')) {
+ /**
+ * Session管理
+ * @param string $name session名称
+ * @param mixed $value session值
+ * @return mixed
+ */
+ function session($name = '', $value = '')
+ {
+ if (is_null($name)) {
+ // 清除
+ Session::clear();
+ } elseif ('' === $name) {
+ return Session::all();
+ } elseif (is_null($value)) {
+ // 删除
+ Session::delete($name);
+ } elseif ('' === $value) {
+ // 判断或获取
+ return 0 === strpos($name, '?') ? Session::has(substr($name, 1)) : Session::get($name);
+ } else {
+ // 设置
+ Session::set($name, $value);
+ }
+ }
+}
+
+if (!function_exists('token')) {
+ /**
+ * 获取Token令牌
+ * @param string $name 令牌名称
+ * @param mixed $type 令牌生成方法
+ * @return string
+ */
+ function token(string $name = '__token__', string $type = 'md5'): string
+ {
+ return Request::buildToken($name, $type);
+ }
+}
+
+if (!function_exists('token_field')) {
+ /**
+ * 生成令牌隐藏表单
+ * @param string $name 令牌名称
+ * @param mixed $type 令牌生成方法
+ * @return string
+ */
+ function token_field(string $name = '__token__', string $type = 'md5'): string
+ {
+ $token = Request::buildToken($name, $type);
+
+ return ' ';
+ }
+}
+
+if (!function_exists('token_meta')) {
+ /**
+ * 生成令牌meta
+ * @param string $name 令牌名称
+ * @param mixed $type 令牌生成方法
+ * @return string
+ */
+ function token_meta(string $name = '__token__', string $type = 'md5'): string
+ {
+ $token = Request::buildToken($name, $type);
+
+ return ' ';
+ }
+}
+
+if (!function_exists('trace')) {
+ /**
+ * 记录日志信息
+ * @param mixed $log log信息 支持字符串和数组
+ * @param string $level 日志级别
+ * @return array|void
+ */
+ function trace($log = '[think]', string $level = 'log')
+ {
+ if ('[think]' === $log) {
+ return Log::getLog();
+ }
+
+ Log::record($log, $level);
+ }
+}
+
+if (!function_exists('url')) {
+ /**
+ * Url生成
+ * @param string $url 路由地址
+ * @param array $vars 变量
+ * @param bool|string $suffix 生成的URL后缀
+ * @param bool|string $domain 域名
+ * @return UrlBuild
+ */
+ function url(string $url = '', array $vars = [], $suffix = true, $domain = false): UrlBuild
+ {
+ return Route::buildUrl($url, $vars)->suffix($suffix)->domain($domain);
+ }
+}
+
+if (!function_exists('validate')) {
+ /**
+ * 生成验证对象
+ * @param string|array $validate 验证器类名或者验证规则数组
+ * @param array $message 错误提示信息
+ * @param bool $batch 是否批量验证
+ * @param bool $failException 是否抛出异常
+ * @return Validate
+ */
+ function validate($validate = '', array $message = [], bool $batch = false, bool $failException = true): Validate
+ {
+ if (is_array($validate) || '' === $validate) {
+ $v = new Validate();
+ if (is_array($validate)) {
+ $v->rule($validate);
+ }
+ } else {
+ if (strpos($validate, '.')) {
+ // 支持场景
+ [$validate, $scene] = explode('.', $validate);
+ }
+
+ $class = false !== strpos($validate, '\\') ? $validate : app()->parseClass('validate', $validate);
+
+ $v = new $class();
+
+ if (!empty($scene)) {
+ $v->scene($scene);
+ }
+ }
+
+ return $v->message($message)->batch($batch)->failException($failException);
+ }
+}
+
+if (!function_exists('view')) {
+ /**
+ * 渲染模板输出
+ * @param string $template 模板文件
+ * @param array $vars 模板变量
+ * @param int $code 状态码
+ * @param callable $filter 内容过滤
+ * @return \think\response\View
+ */
+ function view(string $template = '', $vars = [], $code = 200, $filter = null): View
+ {
+ return Response::create($template, 'view', $code)->assign($vars)->filter($filter);
+ }
+}
+
+if (!function_exists('display')) {
+ /**
+ * 渲染模板输出
+ * @param string $content 渲染内容
+ * @param array $vars 模板变量
+ * @param int $code 状态码
+ * @param callable $filter 内容过滤
+ * @return \think\response\View
+ */
+ function display(string $content, $vars = [], $code = 200, $filter = null): View
+ {
+ return Response::create($content, 'view', $code)->isContent(true)->assign($vars)->filter($filter);
+ }
+}
+
+if (!function_exists('xml')) {
+ /**
+ * 获取\think\response\Xml对象实例
+ * @param mixed $data 返回的数据
+ * @param int $code 状态码
+ * @param array $header 头部
+ * @param array $options 参数
+ * @return \think\response\Xml
+ */
+ function xml($data = [], $code = 200, $header = [], $options = []): Xml
+ {
+ return Response::create($data, 'xml', $code)->header($header)->options($options);
+ }
+}
+
+if (!function_exists('app_path')) {
+ /**
+ * 获取当前应用目录
+ *
+ * @param string $path
+ * @return string
+ */
+ function app_path($path = '')
+ {
+ return app()->getAppPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
+ }
+}
+
+if (!function_exists('base_path')) {
+ /**
+ * 获取应用基础目录
+ *
+ * @param string $path
+ * @return string
+ */
+ function base_path($path = '')
+ {
+ return app()->getBasePath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
+ }
+}
+
+if (!function_exists('config_path')) {
+ /**
+ * 获取应用配置目录
+ *
+ * @param string $path
+ * @return string
+ */
+ function config_path($path = '')
+ {
+ return app()->getConfigPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
+ }
+}
+
+if (!function_exists('public_path')) {
+ /**
+ * 获取web根目录
+ *
+ * @param string $path
+ * @return string
+ */
+ function public_path($path = '')
+ {
+ return app()->getRootPath() . 'public' . DIRECTORY_SEPARATOR . ($path ? ltrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $path);
+ }
+}
+
+if (!function_exists('runtime_path')) {
+ /**
+ * 获取应用运行时目录
+ *
+ * @param string $path
+ * @return string
+ */
+ function runtime_path($path = '')
+ {
+ return app()->getRuntimePath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
+ }
+}
+
+if (!function_exists('root_path')) {
+ /**
+ * 获取项目根目录
+ *
+ * @param string $path
+ * @return string
+ */
+ function root_path($path = '')
+ {
+ return app()->getRootPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
+ }
+}
diff --git a/thinkphp/lang/zh-cn.php b/vendor/topthink/framework/src/lang/zh-cn.php
old mode 100755
new mode 100644
similarity index 94%
rename from thinkphp/lang/zh-cn.php
rename to vendor/topthink/framework/src/lang/zh-cn.php
index 1e0508204..a546330a0
--- a/thinkphp/lang/zh-cn.php
+++ b/vendor/topthink/framework/src/lang/zh-cn.php
@@ -2,7 +2,7 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
@@ -25,8 +25,7 @@ return [
'method param miss' => '方法参数错误',
'method not exists' => '方法不存在',
'function not exists' => '函数不存在',
- 'file not exists' => '文件不存在',
- 'module not exists' => '模块不存在',
+ 'app not exists' => '应用不存在',
'controller not exists' => '控制器不存在',
'class not exists' => '类不存在',
'property not exists' => '类的属性不存在',
@@ -34,15 +33,20 @@ return [
'illegal controller name' => '非法的控制器名称',
'illegal action name' => '非法的操作名称',
'url suffix deny' => '禁止的URL后缀访问',
+ 'Undefined cache config' => '缓存配置未定义',
'Route Not Found' => '当前访问路由未定义或不匹配',
+ 'Undefined db config' => '数据库配置未定义',
+ 'Undefined log config' => '日志配置未定义',
'Undefined db type' => '未定义数据库类型',
'variable type error' => '变量类型错误',
'PSR-4 error' => 'PSR-4 规范错误',
+ 'not support type' => '不支持的分页索引字段类型',
'not support total' => '简洁模式下不能获取数据总数',
'not support last' => '简洁模式下不能获取最后一页',
'error session handler' => '错误的SESSION处理器类',
'not allow php tag' => '模板不允许使用PHP语法',
'not support' => '不支持',
+ 'database config error' => '数据库配置信息错误',
'redisd master' => 'Redisd 主服务器错误',
'redisd slave' => 'Redisd 从服务器错误',
'must run at sae' => '必须在SAE运行',
@@ -50,10 +54,8 @@ return [
'KVDB init error' => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务',
'fields not exists' => '数据表字段不存在',
'where express error' => '查询表达式错误',
- 'order express error' => '排序表达式错误',
'no data to update' => '没有任何数据需要更新',
'miss data to insert' => '缺少需要写入的数据',
- 'not support data' => '不支持的数据表达式',
'miss complex primary data' => '缺少复合主键数据',
'miss update condition' => '缺少更新条件',
'model data Not Found' => '模型数据不存在',
@@ -141,4 +143,6 @@ return [
'invalid Request method' => '无效的请求类型',
'invalid token' => '令牌数据无效',
'not conform to the rules' => '规则错误',
+
+ 'record has update' => '记录已经被更新了',
];
diff --git a/vendor/topthink/framework/src/think/App.php b/vendor/topthink/framework/src/think/App.php
new file mode 100644
index 000000000..cdcfeb8d2
--- /dev/null
+++ b/vendor/topthink/framework/src/think/App.php
@@ -0,0 +1,639 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use think\event\AppInit;
+use think\helper\Str;
+use think\initializer\BootService;
+use think\initializer\Error;
+use think\initializer\RegisterService;
+
+/**
+ * App 基础类
+ * @property Route $route
+ * @property Config $config
+ * @property Cache $cache
+ * @property Request $request
+ * @property Http $http
+ * @property Console $console
+ * @property Env $env
+ * @property Event $event
+ * @property Middleware $middleware
+ * @property Log $log
+ * @property Lang $lang
+ * @property Db $db
+ * @property Cookie $cookie
+ * @property Session $session
+ * @property Validate $validate
+ * @property Filesystem $filesystem
+ */
+class App extends Container
+{
+ const VERSION = '6.0.8';
+
+ /**
+ * 应用调试模式
+ * @var bool
+ */
+ protected $appDebug = false;
+
+ /**
+ * 环境变量标识
+ * @var string
+ */
+ protected $envName = '';
+
+ /**
+ * 应用开始时间
+ * @var float
+ */
+ protected $beginTime;
+
+ /**
+ * 应用内存初始占用
+ * @var integer
+ */
+ protected $beginMem;
+
+ /**
+ * 当前应用类库命名空间
+ * @var string
+ */
+ protected $namespace = 'app';
+
+ /**
+ * 应用根目录
+ * @var string
+ */
+ protected $rootPath = '';
+
+ /**
+ * 框架目录
+ * @var string
+ */
+ protected $thinkPath = '';
+
+ /**
+ * 应用目录
+ * @var string
+ */
+ protected $appPath = '';
+
+ /**
+ * Runtime目录
+ * @var string
+ */
+ protected $runtimePath = '';
+
+ /**
+ * 路由定义目录
+ * @var string
+ */
+ protected $routePath = '';
+
+ /**
+ * 配置后缀
+ * @var string
+ */
+ protected $configExt = '.php';
+
+ /**
+ * 应用初始化器
+ * @var array
+ */
+ protected $initializers = [
+ Error::class,
+ RegisterService::class,
+ BootService::class,
+ ];
+
+ /**
+ * 注册的系统服务
+ * @var array
+ */
+ protected $services = [];
+
+ /**
+ * 初始化
+ * @var bool
+ */
+ protected $initialized = false;
+
+ /**
+ * 容器绑定标识
+ * @var array
+ */
+ protected $bind = [
+ 'app' => App::class,
+ 'cache' => Cache::class,
+ 'config' => Config::class,
+ 'console' => Console::class,
+ 'cookie' => Cookie::class,
+ 'db' => Db::class,
+ 'env' => Env::class,
+ 'event' => Event::class,
+ 'http' => Http::class,
+ 'lang' => Lang::class,
+ 'log' => Log::class,
+ 'middleware' => Middleware::class,
+ 'request' => Request::class,
+ 'response' => Response::class,
+ 'route' => Route::class,
+ 'session' => Session::class,
+ 'validate' => Validate::class,
+ 'view' => View::class,
+ 'filesystem' => Filesystem::class,
+ 'think\DbManager' => Db::class,
+ 'think\LogManager' => Log::class,
+ 'think\CacheManager' => Cache::class,
+
+ // 接口依赖注入
+ 'Psr\Log\LoggerInterface' => Log::class,
+ ];
+
+ /**
+ * 架构方法
+ * @access public
+ * @param string $rootPath 应用根目录
+ */
+ public function __construct(string $rootPath = '')
+ {
+ $this->thinkPath = dirname(__DIR__) . DIRECTORY_SEPARATOR;
+ $this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
+ $this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
+ $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
+
+ if (is_file($this->appPath . 'provider.php')) {
+ $this->bind(include $this->appPath . 'provider.php');
+ }
+
+ static::setInstance($this);
+
+ $this->instance('app', $this);
+ $this->instance('think\Container', $this);
+ }
+
+ /**
+ * 注册服务
+ * @access public
+ * @param Service|string $service 服务
+ * @param bool $force 强制重新注册
+ * @return Service|null
+ */
+ public function register($service, bool $force = false)
+ {
+ $registered = $this->getService($service);
+
+ if ($registered && !$force) {
+ return $registered;
+ }
+
+ if (is_string($service)) {
+ $service = new $service($this);
+ }
+
+ if (method_exists($service, 'register')) {
+ $service->register();
+ }
+
+ if (property_exists($service, 'bind')) {
+ $this->bind($service->bind);
+ }
+
+ $this->services[] = $service;
+ }
+
+ /**
+ * 执行服务
+ * @access public
+ * @param Service $service 服务
+ * @return mixed
+ */
+ public function bootService($service)
+ {
+ if (method_exists($service, 'boot')) {
+ return $this->invoke([$service, 'boot']);
+ }
+ }
+
+ /**
+ * 获取服务
+ * @param string|Service $service
+ * @return Service|null
+ */
+ public function getService($service)
+ {
+ $name = is_string($service) ? $service : get_class($service);
+ return array_values(array_filter($this->services, function ($value) use ($name) {
+ return $value instanceof $name;
+ }, ARRAY_FILTER_USE_BOTH))[0] ?? null;
+ }
+
+ /**
+ * 开启应用调试模式
+ * @access public
+ * @param bool $debug 开启应用调试模式
+ * @return $this
+ */
+ public function debug(bool $debug = true)
+ {
+ $this->appDebug = $debug;
+ return $this;
+ }
+
+ /**
+ * 是否为调试模式
+ * @access public
+ * @return bool
+ */
+ public function isDebug(): bool
+ {
+ return $this->appDebug;
+ }
+
+ /**
+ * 设置应用命名空间
+ * @access public
+ * @param string $namespace 应用命名空间
+ * @return $this
+ */
+ public function setNamespace(string $namespace)
+ {
+ $this->namespace = $namespace;
+ return $this;
+ }
+
+ /**
+ * 获取应用类库命名空间
+ * @access public
+ * @return string
+ */
+ public function getNamespace(): string
+ {
+ return $this->namespace;
+ }
+
+ /**
+ * 设置环境变量标识
+ * @access public
+ * @param string $name 环境标识
+ * @return $this
+ */
+ public function setEnvName(string $name)
+ {
+ $this->envName = $name;
+ return $this;
+ }
+
+ /**
+ * 获取框架版本
+ * @access public
+ * @return string
+ */
+ public function version(): string
+ {
+ return static::VERSION;
+ }
+
+ /**
+ * 获取应用根目录
+ * @access public
+ * @return string
+ */
+ public function getRootPath(): string
+ {
+ return $this->rootPath;
+ }
+
+ /**
+ * 获取应用基础目录
+ * @access public
+ * @return string
+ */
+ public function getBasePath(): string
+ {
+ return $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
+ }
+
+ /**
+ * 获取当前应用目录
+ * @access public
+ * @return string
+ */
+ public function getAppPath(): string
+ {
+ return $this->appPath;
+ }
+
+ /**
+ * 设置应用目录
+ * @param string $path 应用目录
+ */
+ public function setAppPath(string $path)
+ {
+ $this->appPath = $path;
+ }
+
+ /**
+ * 获取应用运行时目录
+ * @access public
+ * @return string
+ */
+ public function getRuntimePath(): string
+ {
+ return $this->runtimePath;
+ }
+
+ /**
+ * 设置runtime目录
+ * @param string $path 定义目录
+ */
+ public function setRuntimePath(string $path): void
+ {
+ $this->runtimePath = $path;
+ }
+
+ /**
+ * 获取核心框架目录
+ * @access public
+ * @return string
+ */
+ public function getThinkPath(): string
+ {
+ return $this->thinkPath;
+ }
+
+ /**
+ * 获取应用配置目录
+ * @access public
+ * @return string
+ */
+ public function getConfigPath(): string
+ {
+ return $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
+ }
+
+ /**
+ * 获取配置后缀
+ * @access public
+ * @return string
+ */
+ public function getConfigExt(): string
+ {
+ return $this->configExt;
+ }
+
+ /**
+ * 获取应用开启时间
+ * @access public
+ * @return float
+ */
+ public function getBeginTime(): float
+ {
+ return $this->beginTime;
+ }
+
+ /**
+ * 获取应用初始内存占用
+ * @access public
+ * @return integer
+ */
+ public function getBeginMem(): int
+ {
+ return $this->beginMem;
+ }
+
+ /**
+ * 加载环境变量定义
+ * @access public
+ * @param string $envName 环境标识
+ * @return void
+ */
+ public function loadEnv(string $envName = ''): void
+ {
+ // 加载环境变量
+ $envFile = $envName ? $this->rootPath . '.env.' . $envName : $this->rootPath . '.env';
+
+ if (is_file($envFile)) {
+ $this->env->load($envFile);
+ }
+ }
+
+ /**
+ * 初始化应用
+ * @access public
+ * @return $this
+ */
+ public function initialize()
+ {
+ $this->initialized = true;
+
+ $this->beginTime = microtime(true);
+ $this->beginMem = memory_get_usage();
+
+ $this->loadEnv($this->envName);
+
+ $this->configExt = $this->env->get('config_ext', '.php');
+
+ $this->debugModeInit();
+
+ // 加载全局初始化文件
+ $this->load();
+
+ // 加载框架默认语言包
+ $langSet = $this->lang->defaultLangSet();
+
+ $this->lang->load($this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php');
+
+ // 加载应用默认语言包
+ $this->loadLangPack($langSet);
+
+ // 监听AppInit
+ $this->event->trigger(AppInit::class);
+
+ date_default_timezone_set($this->config->get('app.default_timezone', 'Asia/Shanghai'));
+
+ // 初始化
+ foreach ($this->initializers as $initializer) {
+ $this->make($initializer)->init($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 是否初始化过
+ * @return bool
+ */
+ public function initialized()
+ {
+ return $this->initialized;
+ }
+
+ /**
+ * 加载语言包
+ * @param string $langset 语言
+ * @return void
+ */
+ public function loadLangPack($langset)
+ {
+ if (empty($langset)) {
+ return;
+ }
+
+ // 加载系统语言包
+ $files = glob($this->appPath . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*');
+ $this->lang->load($files);
+
+ // 加载扩展(自定义)语言包
+ $list = $this->config->get('lang.extend_list', []);
+
+ if (isset($list[$langset])) {
+ $this->lang->load($list[$langset]);
+ }
+ }
+
+ /**
+ * 引导应用
+ * @access public
+ * @return void
+ */
+ public function boot(): void
+ {
+ array_walk($this->services, function ($service) {
+ $this->bootService($service);
+ });
+ }
+
+ /**
+ * 加载应用文件和配置
+ * @access protected
+ * @return void
+ */
+ protected function load(): void
+ {
+ $appPath = $this->getAppPath();
+
+ if (is_file($appPath . 'common.php')) {
+ include_once $appPath . 'common.php';
+ }
+
+ include_once $this->thinkPath . 'helper.php';
+
+ $configPath = $this->getConfigPath();
+
+ $files = [];
+
+ if (is_dir($configPath)) {
+ $files = glob($configPath . '*' . $this->configExt);
+ }
+
+ foreach ($files as $file) {
+ $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
+ }
+
+ if (is_file($appPath . 'event.php')) {
+ $this->loadEvent(include $appPath . 'event.php');
+ }
+
+ if (is_file($appPath . 'service.php')) {
+ $services = include $appPath . 'service.php';
+ foreach ($services as $service) {
+ $this->register($service);
+ }
+ }
+ }
+
+ /**
+ * 调试模式设置
+ * @access protected
+ * @return void
+ */
+ protected function debugModeInit(): void
+ {
+ // 应用调试模式
+ if (!$this->appDebug) {
+ $this->appDebug = $this->env->get('app_debug') ? true : false;
+ ini_set('display_errors', 'Off');
+ }
+
+ if (!$this->runningInConsole()) {
+ //重新申请一块比较大的buffer
+ if (ob_get_level() > 0) {
+ $output = ob_get_clean();
+ }
+ ob_start();
+ if (!empty($output)) {
+ echo $output;
+ }
+ }
+ }
+
+ /**
+ * 注册应用事件
+ * @access protected
+ * @param array $event 事件数据
+ * @return void
+ */
+ public function loadEvent(array $event): void
+ {
+ if (isset($event['bind'])) {
+ $this->event->bind($event['bind']);
+ }
+
+ if (isset($event['listen'])) {
+ $this->event->listenEvents($event['listen']);
+ }
+
+ if (isset($event['subscribe'])) {
+ $this->event->subscribe($event['subscribe']);
+ }
+ }
+
+ /**
+ * 解析应用类的类名
+ * @access public
+ * @param string $layer 层名 controller model ...
+ * @param string $name 类名
+ * @return string
+ */
+ public function parseClass(string $layer, string $name): string
+ {
+ $name = str_replace(['/', '.'], '\\', $name);
+ $array = explode('\\', $name);
+ $class = Str::studly(array_pop($array));
+ $path = $array ? implode('\\', $array) . '\\' : '';
+
+ return $this->namespace . '\\' . $layer . '\\' . $path . $class;
+ }
+
+ /**
+ * 是否运行在命令行下
+ * @return bool
+ */
+ public function runningInConsole(): bool
+ {
+ return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg';
+ }
+
+ /**
+ * 获取应用根目录
+ * @access protected
+ * @return string
+ */
+ protected function getDefaultRootPath(): string
+ {
+ return dirname($this->thinkPath, 4) . DIRECTORY_SEPARATOR;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/Cache.php b/vendor/topthink/framework/src/think/Cache.php
new file mode 100644
index 000000000..f802b556f
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Cache.php
@@ -0,0 +1,197 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Psr\SimpleCache\CacheInterface;
+use think\cache\Driver;
+use think\cache\TagSet;
+use think\exception\InvalidArgumentException;
+use think\helper\Arr;
+
+/**
+ * 缓存管理类
+ * @mixin Driver
+ * @mixin \think\cache\driver\File
+ */
+class Cache extends Manager implements CacheInterface
+{
+
+ protected $namespace = '\\think\\cache\\driver\\';
+
+ /**
+ * 默认驱动
+ * @return string|null
+ */
+ public function getDefaultDriver()
+ {
+ return $this->getConfig('default');
+ }
+
+ /**
+ * 获取缓存配置
+ * @access public
+ * @param null|string $name 名称
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function getConfig(string $name = null, $default = null)
+ {
+ if (!is_null($name)) {
+ return $this->app->config->get('cache.' . $name, $default);
+ }
+
+ return $this->app->config->get('cache');
+ }
+
+ /**
+ * 获取驱动配置
+ * @param string $store
+ * @param string $name
+ * @param null $default
+ * @return array
+ */
+ public function getStoreConfig(string $store, string $name = null, $default = null)
+ {
+ if ($config = $this->getConfig("stores.{$store}")) {
+ return Arr::get($config, $name, $default);
+ }
+
+ throw new \InvalidArgumentException("Store [$store] not found.");
+ }
+
+ protected function resolveType(string $name)
+ {
+ return $this->getStoreConfig($name, 'type', 'file');
+ }
+
+ protected function resolveConfig(string $name)
+ {
+ return $this->getStoreConfig($name);
+ }
+
+ /**
+ * 连接或者切换缓存
+ * @access public
+ * @param string $name 连接配置名
+ * @return Driver
+ */
+ public function store(string $name = null)
+ {
+ return $this->driver($name);
+ }
+
+ /**
+ * 清空缓冲池
+ * @access public
+ * @return bool
+ */
+ public function clear(): bool
+ {
+ return $this->store()->clear();
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $key 缓存变量名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ return $this->store()->get($key, $default);
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $key 缓存变量名
+ * @param mixed $value 存储数据
+ * @param int|\DateTime $ttl 有效时间 0为永久
+ * @return bool
+ */
+ public function set($key, $value, $ttl = null): bool
+ {
+ return $this->store()->set($key, $value, $ttl);
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $key 缓存变量名
+ * @return bool
+ */
+ public function delete($key): bool
+ {
+ return $this->store()->delete($key);
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param iterable $keys 缓存变量名
+ * @param mixed $default 默认值
+ * @return iterable
+ * @throws InvalidArgumentException
+ */
+ public function getMultiple($keys, $default = null): iterable
+ {
+ return $this->store()->getMultiple($keys, $default);
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param iterable $values 缓存数据
+ * @param null|int|\DateInterval $ttl 有效时间 0为永久
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null): bool
+ {
+ return $this->store()->setMultiple($values, $ttl);
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param iterable $keys 缓存变量名
+ * @return bool
+ * @throws InvalidArgumentException
+ */
+ public function deleteMultiple($keys): bool
+ {
+ return $this->store()->deleteMultiple($keys);
+ }
+
+ /**
+ * 判断缓存是否存在
+ * @access public
+ * @param string $key 缓存变量名
+ * @return bool
+ */
+ public function has($key): bool
+ {
+ return $this->store()->has($key);
+ }
+
+ /**
+ * 缓存标签
+ * @access public
+ * @param string|array $name 标签名
+ * @return TagSet
+ */
+ public function tag($name): TagSet
+ {
+ return $this->store()->tag($name);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Config.php b/vendor/topthink/framework/src/think/Config.php
new file mode 100644
index 000000000..9162e82fd
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Config.php
@@ -0,0 +1,197 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+/**
+ * 配置管理类
+ * @package think
+ */
+class Config
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [];
+
+ /**
+ * 配置文件目录
+ * @var string
+ */
+ protected $path;
+
+ /**
+ * 配置文件后缀
+ * @var string
+ */
+ protected $ext;
+
+ /**
+ * 构造方法
+ * @access public
+ */
+ public function __construct(string $path = null, string $ext = '.php')
+ {
+ $this->path = $path ?: '';
+ $this->ext = $ext;
+ }
+
+ public static function __make(App $app)
+ {
+ $path = $app->getConfigPath();
+ $ext = $app->getConfigExt();
+
+ return new static($path, $ext);
+ }
+
+ /**
+ * 加载配置文件(多种格式)
+ * @access public
+ * @param string $file 配置文件名
+ * @param string $name 一级配置名
+ * @return array
+ */
+ public function load(string $file, string $name = ''): array
+ {
+ if (is_file($file)) {
+ $filename = $file;
+ } elseif (is_file($this->path . $file . $this->ext)) {
+ $filename = $this->path . $file . $this->ext;
+ }
+
+ if (isset($filename)) {
+ return $this->parse($filename, $name);
+ }
+
+ return $this->config;
+ }
+
+ /**
+ * 解析配置文件
+ * @access public
+ * @param string $file 配置文件名
+ * @param string $name 一级配置名
+ * @return array
+ */
+ protected function parse(string $file, string $name): array
+ {
+ $type = pathinfo($file, PATHINFO_EXTENSION);
+ $config = [];
+ switch ($type) {
+ case 'php':
+ $config = include $file;
+ break;
+ case 'yml':
+ case 'yaml':
+ if (function_exists('yaml_parse_file')) {
+ $config = yaml_parse_file($file);
+ }
+ break;
+ case 'ini':
+ $config = parse_ini_file($file, true, INI_SCANNER_TYPED) ?: [];
+ break;
+ case 'json':
+ $config = json_decode(file_get_contents($file), true);
+ break;
+ }
+
+ return is_array($config) ? $this->set($config, strtolower($name)) : [];
+ }
+
+ /**
+ * 检测配置是否存在
+ * @access public
+ * @param string $name 配置参数名(支持多级配置 .号分割)
+ * @return bool
+ */
+ public function has(string $name): bool
+ {
+ if (false === strpos($name, '.') && !isset($this->config[strtolower($name)])) {
+ return false;
+ }
+
+ return !is_null($this->get($name));
+ }
+
+ /**
+ * 获取一级配置
+ * @access protected
+ * @param string $name 一级配置名
+ * @return array
+ */
+ protected function pull(string $name): array
+ {
+ $name = strtolower($name);
+
+ return $this->config[$name] ?? [];
+ }
+
+ /**
+ * 获取配置参数 为空则获取所有配置
+ * @access public
+ * @param string $name 配置参数名(支持多级配置 .号分割)
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get(string $name = null, $default = null)
+ {
+ // 无参数时获取所有
+ if (empty($name)) {
+ return $this->config;
+ }
+
+ if (false === strpos($name, '.')) {
+ return $this->pull($name);
+ }
+
+ $name = explode('.', $name);
+ $name[0] = strtolower($name[0]);
+ $config = $this->config;
+
+ // 按.拆分成多维数组进行判断
+ foreach ($name as $val) {
+ if (isset($config[$val])) {
+ $config = $config[$val];
+ } else {
+ return $default;
+ }
+ }
+
+ return $config;
+ }
+
+ /**
+ * 设置配置参数 name为数组则为批量设置
+ * @access public
+ * @param array $config 配置参数
+ * @param string $name 配置名
+ * @return array
+ */
+ public function set(array $config, string $name = null): array
+ {
+ if (!empty($name)) {
+ if (isset($this->config[$name])) {
+ $result = array_merge($this->config[$name], $config);
+ } else {
+ $result = $config;
+ }
+
+ $this->config[$name] = $result;
+ } else {
+ $result = $this->config = array_merge($this->config, array_change_key_case($config));
+ }
+
+ return $result;
+ }
+
+}
diff --git a/thinkphp/library/think/Console.php b/vendor/topthink/framework/src/think/Console.php
old mode 100755
new mode 100644
similarity index 55%
rename from thinkphp/library/think/Console.php
rename to vendor/topthink/framework/src/think/Console.php
index d12129072..389d104d5
--- a/thinkphp/library/think/Console.php
+++ b/vendor/topthink/framework/src/think/Console.php
@@ -6,11 +6,34 @@
// +----------------------------------------------------------------------
// | Author: zhangyajun <448901948@qq.com>
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think;
+use Closure;
+use InvalidArgumentException;
+use LogicException;
use think\console\Command;
+use think\console\command\Clear;
+use think\console\command\Help;
use think\console\command\Help as HelpCommand;
+use think\console\command\Lists;
+use think\console\command\make\Command as MakeCommand;
+use think\console\command\make\Controller;
+use think\console\command\make\Event;
+use think\console\command\make\Listener;
+use think\console\command\make\Middleware;
+use think\console\command\make\Model;
+use think\console\command\make\Service;
+use think\console\command\make\Subscribe;
+use think\console\command\make\Validate;
+use think\console\command\optimize\Route;
+use think\console\command\optimize\Schema;
+use think\console\command\RouteList;
+use think\console\command\RunServer;
+use think\console\command\ServiceDiscover;
+use think\console\command\VendorPublish;
+use think\console\command\Version;
use think\console\Input;
use think\console\input\Argument as InputArgument;
use think\console\input\Definition as InputDefinition;
@@ -18,164 +41,194 @@ use think\console\input\Option as InputOption;
use think\console\Output;
use think\console\output\driver\Buffer;
+/**
+ * 控制台应用管理类
+ */
class Console
{
- private $name;
- private $version;
+ protected $app;
/** @var Command[] */
- private $commands = [];
+ protected $commands = [];
- private $wantHelps = false;
+ protected $wantHelps = false;
- private $catchExceptions = true;
- private $autoExit = true;
- private $definition;
- private $defaultCommand;
+ protected $catchExceptions = true;
+ protected $autoExit = true;
+ protected $definition;
+ protected $defaultCommand = 'list';
- private static $defaultCommands = [
- 'help' => "think\\console\\command\\Help",
- 'list' => "think\\console\\command\\Lists",
- 'build' => "think\\console\\command\\Build",
- 'clear' => "think\\console\\command\\Clear",
- 'make:command' => "think\\console\\command\\make\\Command",
- 'make:controller' => "think\\console\\command\\make\\Controller",
- 'make:model' => "think\\console\\command\\make\\Model",
- 'make:middleware' => "think\\console\\command\\make\\Middleware",
- 'make:validate' => "think\\console\\command\\make\\Validate",
- 'optimize:autoload' => "think\\console\\command\\optimize\\Autoload",
- 'optimize:config' => "think\\console\\command\\optimize\\Config",
- 'optimize:schema' => "think\\console\\command\\optimize\\Schema",
- 'optimize:route' => "think\\console\\command\\optimize\\Route",
- 'run' => "think\\console\\command\\RunServer",
- 'version' => "think\\console\\command\\Version",
- 'route:list' => "think\\console\\command\\RouteList",
+ protected $defaultCommands = [
+ 'help' => Help::class,
+ 'list' => Lists::class,
+ 'clear' => Clear::class,
+ 'make:command' => MakeCommand::class,
+ 'make:controller' => Controller::class,
+ 'make:model' => Model::class,
+ 'make:middleware' => Middleware::class,
+ 'make:validate' => Validate::class,
+ 'make:event' => Event::class,
+ 'make:listener' => Listener::class,
+ 'make:service' => Service::class,
+ 'make:subscribe' => Subscribe::class,
+ 'optimize:route' => Route::class,
+ 'optimize:schema' => Schema::class,
+ 'run' => RunServer::class,
+ 'version' => Version::class,
+ 'route:list' => RouteList::class,
+ 'service:discover' => ServiceDiscover::class,
+ 'vendor:publish' => VendorPublish::class,
];
/**
- * Console constructor.
- * @access public
- * @param string $name 名称
- * @param string $version 版本
- * @param null|string $user 执行用户
+ * 启动器
+ * @var array
*/
- public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN', $user = null)
- {
- $this->name = $name;
- $this->version = $version;
+ protected static $startCallbacks = [];
- if ($user) {
- $this->setUser($user);
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+
+ $this->initialize();
+
+ $this->definition = $this->getDefaultInputDefinition();
+
+ //加载指令
+ $this->loadCommands();
+
+ $this->start();
+ }
+
+ /**
+ * 初始化
+ */
+ protected function initialize()
+ {
+ if (!$this->app->initialized()) {
+ $this->app->initialize();
+ }
+ $this->makeRequest();
+ }
+
+ /**
+ * 构造request
+ */
+ protected function makeRequest()
+ {
+ $uri = $this->app->config->get('app.url', 'http://localhost');
+
+ $components = parse_url($uri);
+
+ $server = $_SERVER;
+
+ if (isset($components['path'])) {
+ $server = array_merge($server, [
+ 'SCRIPT_FILENAME' => $components['path'],
+ 'SCRIPT_NAME' => $components['path'],
+ ]);
}
- $this->defaultCommand = 'list';
- $this->definition = $this->getDefaultInputDefinition();
+ if (isset($components['host'])) {
+ $server['SERVER_NAME'] = $components['host'];
+ $server['HTTP_HOST'] = $components['host'];
+ }
+
+ if (isset($components['scheme'])) {
+ if ('https' === $components['scheme']) {
+ $server['HTTPS'] = 'on';
+ $server['SERVER_PORT'] = 443;
+ } else {
+ unset($server['HTTPS']);
+ $server['SERVER_PORT'] = 80;
+ }
+ }
+
+ if (isset($components['port'])) {
+ $server['SERVER_PORT'] = $components['port'];
+ $server['HTTP_HOST'] .= ':' . $components['port'];
+ }
+
+ $server['REQUEST_URI'] = $uri;
+
+ /** @var Request $request */
+ $request = $this->app->make('request');
+
+ $request->withServer($server);
+ }
+
+ /**
+ * 添加初始化器
+ * @param Closure $callback
+ */
+ public static function starting(Closure $callback): void
+ {
+ static::$startCallbacks[] = $callback;
+ }
+
+ /**
+ * 清空启动器
+ */
+ public static function flushStartCallbacks(): void
+ {
+ static::$startCallbacks = [];
}
/**
* 设置执行用户
* @param $user
*/
- public function setUser($user)
+ public static function setUser(string $user): void
{
- if (DIRECTORY_SEPARATOR == '\\') {
- return;
- }
+ if (extension_loaded('posix')) {
+ $user = posix_getpwnam($user);
- $user = posix_getpwnam($user);
- if ($user) {
- posix_setuid($user['uid']);
- posix_setgid($user['gid']);
- }
- }
-
- /**
- * 初始化 Console
- * @access public
- * @param bool $run 是否运行 Console
- * @return int|Console
- */
- public static function init($run = true)
- {
- static $console;
-
- if (!$console) {
- $config = Container::get('config')->pull('console');
- $console = new self($config['name'], $config['version'], $config['user']);
-
- $commands = $console->getDefinedCommands($config);
-
- // 添加指令集
- $console->addCommands($commands);
- }
-
- if ($run) {
- // 运行
- return $console->run();
- } else {
- return $console;
- }
- }
-
- /**
- * @access public
- * @param array $config
- * @return array
- */
- public function getDefinedCommands(array $config = [])
- {
- $commands = self::$defaultCommands;
-
- if (!empty($config['auto_path']) && is_dir($config['auto_path'])) {
- // 自动加载指令类
- $files = scandir($config['auto_path']);
-
- if (count($files) > 2) {
- $beforeClass = get_declared_classes();
-
- foreach ($files as $file) {
- if (pathinfo($file, PATHINFO_EXTENSION) == 'php') {
- include $config['auto_path'] . $file;
- }
- }
-
- $afterClass = get_declared_classes();
- $commands = array_merge($commands, array_diff($afterClass, $beforeClass));
+ if (!empty($user)) {
+ posix_setgid($user['gid']);
+ posix_setuid($user['uid']);
}
}
+ }
- $file = Container::get('env')->get('app_path') . 'command.php';
-
- if (is_file($file)) {
- $appCommands = include $file;
-
- if (is_array($appCommands)) {
- $commands = array_merge($commands, $appCommands);
- }
+ /**
+ * 启动
+ */
+ protected function start(): void
+ {
+ foreach (static::$startCallbacks as $callback) {
+ $callback($this);
}
+ }
- return $commands;
+ /**
+ * 加载指令
+ * @access protected
+ */
+ protected function loadCommands(): void
+ {
+ $commands = $this->app->config->get('console.commands', []);
+ $commands = array_merge($this->defaultCommands, $commands);
+
+ $this->addCommands($commands);
}
/**
* @access public
- * @param string $command
- * @param array $parameters
- * @param string $driver
+ * @param string $command
+ * @param array $parameters
+ * @param string $driver
* @return Output|Buffer
*/
- public static function call($command, array $parameters = [], $driver = 'buffer')
+ public function call(string $command, array $parameters = [], string $driver = 'buffer')
{
- $console = self::init(false);
-
array_unshift($parameters, $command);
$input = new Input($parameters);
$output = new Output($driver);
- $console->setCatchExceptions(false);
- $console->find($command)->run($input, $output);
+ $this->setCatchExceptions(false);
+ $this->find($command)->run($input, $output);
return $output;
}
@@ -228,8 +281,8 @@ class Console
/**
* 执行指令
* @access public
- * @param Input $input
- * @param Output $output
+ * @param Input $input
+ * @param Output $output
* @return int
*/
public function doRun(Input $input, Output $output)
@@ -258,17 +311,15 @@ class Console
$command = $this->find($name);
- $exitCode = $this->doRunCommand($command, $input, $output);
-
- return $exitCode;
+ return $this->doRunCommand($command, $input, $output);
}
/**
* 设置输入参数定义
* @access public
- * @param InputDefinition $definition
+ * @param InputDefinition $definition
*/
- public function setDefinition(InputDefinition $definition)
+ public function setDefinition(InputDefinition $definition): void
{
$this->definition = $definition;
}
@@ -278,7 +329,7 @@ class Console
* @access public
* @return InputDefinition The InputDefinition instance
*/
- public function getDefinition()
+ public function getDefinition(): InputDefinition
{
return $this->definition;
}
@@ -288,7 +339,7 @@ class Console
* @access public
* @return string A help message.
*/
- public function getHelp()
+ public function getHelp(): string
{
return $this->getLongVersion();
}
@@ -296,64 +347,23 @@ class Console
/**
* 是否捕获异常
* @access public
- * @param bool $boolean
+ * @param bool $boolean
* @api
*/
- public function setCatchExceptions($boolean)
+ public function setCatchExceptions(bool $boolean): void
{
- $this->catchExceptions = (bool) $boolean;
+ $this->catchExceptions = $boolean;
}
/**
* 是否自动退出
* @access public
- * @param bool $boolean
+ * @param bool $boolean
* @api
*/
- public function setAutoExit($boolean)
+ public function setAutoExit(bool $boolean): void
{
- $this->autoExit = (bool) $boolean;
- }
-
- /**
- * 获取名称
- * @access public
- * @return string
- */
- public function getName()
- {
- return $this->name;
- }
-
- /**
- * 设置名称
- * @access public
- * @param string $name
- */
- public function setName($name)
- {
- $this->name = $name;
- }
-
- /**
- * 获取版本
- * @access public
- * @return string
- * @api
- */
- public function getVersion()
- {
- return $this->version;
- }
-
- /**
- * 设置版本
- * @access public
- * @param string $version
- */
- public function setVersion($version)
- {
- $this->version = $version;
+ $this->autoExit = $boolean;
}
/**
@@ -361,49 +371,38 @@ class Console
* @access public
* @return string
*/
- public function getLongVersion()
+ public function getLongVersion(): string
{
- if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
- return sprintf('%s version %s ', $this->getName(), $this->getVersion());
+ if ($this->app->version()) {
+ return sprintf('version %s ', $this->app->version());
}
return 'Console Tool ';
}
- /**
- * 注册一个指令 (便于动态创建指令)
- * @access public
- * @param string $name 指令名
- * @return Command
- */
- public function register($name)
- {
- return $this->add(new Command($name));
- }
-
/**
* 添加指令集
* @access public
- * @param array $commands
+ * @param array $commands
*/
- public function addCommands(array $commands)
+ public function addCommands(array $commands): void
{
foreach ($commands as $key => $command) {
- if (is_subclass_of($command, "\\think\\console\\Command")) {
+ if (is_subclass_of($command, Command::class)) {
// 注册指令
- $this->add($command, is_numeric($key) ? '' : $key);
+ $this->addCommand($command, is_numeric($key) ? '' : $key);
}
}
}
/**
- * 注册一个指令(对象)
+ * 添加一个指令
* @access public
- * @param mixed $command 指令对象或者指令类名
- * @param string $name 指令名 留空则自动获取
- * @return mixed
+ * @param string|Command $command 指令对象或者指令类名
+ * @param string $name 指令名 留空则自动获取
+ * @return Command|void
*/
- public function add($command, $name)
+ public function addCommand($command, string $name = '')
{
if ($name) {
$this->commands[$name] = $command;
@@ -411,7 +410,7 @@ class Console
}
if (is_string($command)) {
- $command = new $command();
+ $command = $this->app->invokeClass($command);
}
$command->setConsole($this);
@@ -421,8 +420,10 @@ class Console
return;
}
+ $command->setApp($this->app);
+
if (null === $command->getDefinition()) {
- throw new \LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
+ throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
}
$this->commands[$command->getName()] = $command;
@@ -437,29 +438,30 @@ class Console
/**
* 获取指令
* @access public
- * @param string $name 指令名称
+ * @param string $name 指令名称
* @return Command
- * @throws \InvalidArgumentException
+ * @throws InvalidArgumentException
*/
- public function get($name)
+ public function getCommand(string $name): Command
{
if (!isset($this->commands[$name])) {
- throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
+ throw new InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
}
$command = $this->commands[$name];
if (is_string($command)) {
- $command = new $command();
+ $command = $this->app->invokeClass($command);
+ /** @var Command $command */
+ $command->setConsole($this);
+ $command->setApp($this->app);
}
- $command->setConsole($this);
-
if ($this->wantHelps) {
$this->wantHelps = false;
/** @var HelpCommand $helpCommand */
- $helpCommand = $this->get('help');
+ $helpCommand = $this->getCommand('help');
$helpCommand->setCommand($command);
return $helpCommand;
@@ -471,10 +473,10 @@ class Console
/**
* 某个指令是否存在
* @access public
- * @param string $name 指令名称
+ * @param string $name 指令名称
* @return bool
*/
- public function has($name)
+ public function hasCommand(string $name): bool
{
return isset($this->commands[$name]);
}
@@ -484,14 +486,18 @@ class Console
* @access public
* @return array
*/
- public function getNamespaces()
+ public function getNamespaces(): array
{
$namespaces = [];
- foreach ($this->commands as $command) {
- $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
+ foreach ($this->commands as $key => $command) {
+ if (is_string($command)) {
+ $namespaces = array_merge($namespaces, $this->extractAllNamespaces($key));
+ } else {
+ $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
- foreach ($command->getAliases() as $alias) {
- $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
+ foreach ($command->getAliases() as $alias) {
+ $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
+ }
}
}
@@ -501,17 +507,17 @@ class Console
/**
* 查找注册命名空间中的名称或缩写。
* @access public
- * @param string $namespace
+ * @param string $namespace
* @return string
- * @throws \InvalidArgumentException
+ * @throws InvalidArgumentException
*/
- public function findNamespace($namespace)
+ public function findNamespace(string $namespace): string
{
$allNamespaces = $this->getNamespaces();
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
return preg_quote($matches[1]) . '[^:]*';
}, $namespace);
- $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces);
+ $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces);
if (empty($namespaces)) {
$message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
@@ -526,12 +532,12 @@ class Console
$message .= implode("\n ", $alternatives);
}
- throw new \InvalidArgumentException($message);
+ throw new InvalidArgumentException($message);
}
$exact = in_array($namespace, $namespaces, true);
if (count($namespaces) > 1 && !$exact) {
- throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))));
+ throw new InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))));
}
return $exact ? $namespace : reset($namespaces);
@@ -540,11 +546,11 @@ class Console
/**
* 查找指令
* @access public
- * @param string $name 名称或者别名
+ * @param string $name 名称或者别名
* @return Command
- * @throws \InvalidArgumentException
+ * @throws InvalidArgumentException
*/
- public function find($name)
+ public function find(string $name): Command
{
$allCommands = array_keys($this->commands);
@@ -570,27 +576,27 @@ class Console
$message .= implode("\n ", $alternatives);
}
- throw new \InvalidArgumentException($message);
+ throw new InvalidArgumentException($message);
}
$exact = in_array($name, $commands, true);
if (count($commands) > 1 && !$exact) {
$suggestions = $this->getAbbreviationSuggestions(array_values($commands));
- throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
+ throw new InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
}
- return $this->get($exact ? $name : reset($commands));
+ return $this->getCommand($exact ? $name : reset($commands));
}
/**
* 获取所有的指令
* @access public
- * @param string $namespace 命名空间
+ * @param string $namespace 命名空间
* @return Command[]
* @api
*/
- public function all($namespace = null)
+ public function all(string $namespace = null): array
{
if (null === $namespace) {
return $this->commands;
@@ -606,32 +612,13 @@ class Console
return $commands;
}
- /**
- * 获取可能的指令名
- * @access public
- * @param array $names
- * @return array
- */
- public static function getAbbreviations($names)
- {
- $abbrevs = [];
- foreach ($names as $name) {
- for ($len = strlen($name); $len > 0; --$len) {
- $abbrev = substr($name, 0, $len);
- $abbrevs[$abbrev][] = $name;
- }
- }
-
- return $abbrevs;
- }
-
/**
* 配置基于用户的参数和选项的输入和输出实例。
* @access protected
- * @param Input $input 输入实例
- * @param Output $output 输出实例
+ * @param Input $input 输入实例
+ * @param Output $output 输出实例
*/
- protected function configureIO(Input $input, Output $output)
+ protected function configureIO(Input $input, Output $output): void
{
if (true === $input->hasParameterOption(['--ansi'])) {
$output->setDecorated(true);
@@ -645,23 +632,21 @@ class Console
if (true === $input->hasParameterOption(['--quiet', '-q'])) {
$output->setVerbosity(Output::VERBOSITY_QUIET);
- } else {
- if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) {
- $output->setVerbosity(Output::VERBOSITY_DEBUG);
- } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) {
- $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE);
- } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
- $output->setVerbosity(Output::VERBOSITY_VERBOSE);
- }
+ } elseif ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) {
+ $output->setVerbosity(Output::VERBOSITY_DEBUG);
+ } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) {
+ $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE);
+ } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
+ $output->setVerbosity(Output::VERBOSITY_VERBOSE);
}
}
/**
* 执行指令
* @access protected
- * @param Command $command 指令实例
- * @param Input $input 输入实例
- * @param Output $output 输出实例
+ * @param Command $command 指令实例
+ * @param Input $input 输入实例
+ * @param Output $output 输出实例
* @return int
* @throws \Exception
*/
@@ -673,12 +658,12 @@ class Console
/**
* 获取指令的基础名称
* @access protected
- * @param Input $input
+ * @param Input $input
* @return string
*/
- protected function getCommandName(Input $input)
+ protected function getCommandName(Input $input): string
{
- return $input->getFirstArgument();
+ return $input->getFirstArgument() ?: '';
}
/**
@@ -686,7 +671,7 @@ class Console
* @access protected
* @return InputDefinition
*/
- protected function getDefaultInputDefinition()
+ protected function getDefaultInputDefinition(): InputDefinition
{
return new InputDefinition([
new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
@@ -700,18 +685,13 @@ class Console
]);
}
- public static function addDefaultCommands(array $classnames)
- {
- self::$defaultCommands = array_merge(self::$defaultCommands, $classnames);
- }
-
/**
* 获取可能的建议
* @access private
- * @param array $abbrevs
+ * @param array $abbrevs
* @return string
*/
- private function getAbbreviationSuggestions($abbrevs)
+ private function getAbbreviationSuggestions(array $abbrevs): string
{
return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
}
@@ -719,26 +699,26 @@ class Console
/**
* 返回命名空间部分
* @access public
- * @param string $name 指令
- * @param string $limit 部分的命名空间的最大数量
+ * @param string $name 指令
+ * @param int $limit 部分的命名空间的最大数量
* @return string
*/
- public function extractNamespace($name, $limit = null)
+ public function extractNamespace(string $name, int $limit = 0): string
{
$parts = explode(':', $name);
array_pop($parts);
- return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
+ return implode(':', 0 === $limit ? $parts : array_slice($parts, 0, $limit));
}
/**
* 查找可替代的建议
* @access private
- * @param string $name
- * @param array|\Traversable $collection
+ * @param string $name
+ * @param array|\Traversable $collection
* @return array
*/
- private function findAlternatives($name, $collection)
+ private function findAlternatives(string $name, $collection): array
{
$threshold = 1e3;
$alternatives = [];
@@ -782,23 +762,13 @@ class Console
return array_keys($alternatives);
}
- /**
- * 设置默认的指令
- * @access public
- * @param string $commandName The Command name
- */
- public function setDefaultCommand($commandName)
- {
- $this->defaultCommand = $commandName;
- }
-
/**
* 返回所有的命名空间
* @access private
- * @param string $name
+ * @param string $name
* @return array
*/
- private function extractAllNamespaces($name)
+ private function extractAllNamespaces(string $name): array
{
$parts = explode(':', $name, -1);
$namespaces = [];
@@ -814,11 +784,4 @@ class Console
return $namespaces;
}
- public function __debugInfo()
- {
- $data = get_object_vars($this);
- unset($data['commands'], $data['definition']);
-
- return $data;
- }
}
diff --git a/vendor/topthink/framework/src/think/Container.php b/vendor/topthink/framework/src/think/Container.php
new file mode 100644
index 000000000..74026bb09
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Container.php
@@ -0,0 +1,554 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use ArrayAccess;
+use ArrayIterator;
+use Closure;
+use Countable;
+use InvalidArgumentException;
+use IteratorAggregate;
+use Psr\Container\ContainerInterface;
+use ReflectionClass;
+use ReflectionException;
+use ReflectionFunction;
+use ReflectionFunctionAbstract;
+use ReflectionMethod;
+use think\exception\ClassNotFoundException;
+use think\exception\FuncNotFoundException;
+use think\helper\Str;
+
+/**
+ * 容器管理类 支持PSR-11
+ */
+class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable
+{
+ /**
+ * 容器对象实例
+ * @var Container|Closure
+ */
+ protected static $instance;
+
+ /**
+ * 容器中的对象实例
+ * @var array
+ */
+ protected $instances = [];
+
+ /**
+ * 容器绑定标识
+ * @var array
+ */
+ protected $bind = [];
+
+ /**
+ * 容器回调
+ * @var array
+ */
+ protected $invokeCallback = [];
+
+ /**
+ * 获取当前容器的实例(单例)
+ * @access public
+ * @return static
+ */
+ public static function getInstance()
+ {
+ if (is_null(static::$instance)) {
+ static::$instance = new static;
+ }
+
+ if (static::$instance instanceof Closure) {
+ return (static::$instance)();
+ }
+
+ return static::$instance;
+ }
+
+ /**
+ * 设置当前容器的实例
+ * @access public
+ * @param object|Closure $instance
+ * @return void
+ */
+ public static function setInstance($instance): void
+ {
+ static::$instance = $instance;
+ }
+
+ /**
+ * 注册一个容器对象回调
+ *
+ * @param string|Closure $abstract
+ * @param Closure|null $callback
+ * @return void
+ */
+ public function resolving($abstract, Closure $callback = null): void
+ {
+ if ($abstract instanceof Closure) {
+ $this->invokeCallback['*'][] = $abstract;
+ return;
+ }
+
+ $abstract = $this->getAlias($abstract);
+
+ $this->invokeCallback[$abstract][] = $callback;
+ }
+
+ /**
+ * 获取容器中的对象实例 不存在则创建
+ * @access public
+ * @param string $abstract 类名或者标识
+ * @param array|true $vars 变量
+ * @param bool $newInstance 是否每次创建新的实例
+ * @return object
+ */
+ public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
+ {
+ return static::getInstance()->make($abstract, $vars, $newInstance);
+ }
+
+ /**
+ * 获取容器中的对象实例
+ * @access public
+ * @param string $abstract 类名或者标识
+ * @return object
+ */
+ public function get($abstract)
+ {
+ if ($this->has($abstract)) {
+ return $this->make($abstract);
+ }
+
+ throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
+ }
+
+ /**
+ * 绑定一个类、闭包、实例、接口实现到容器
+ * @access public
+ * @param string|array $abstract 类标识、接口
+ * @param mixed $concrete 要绑定的类、闭包或者实例
+ * @return $this
+ */
+ public function bind($abstract, $concrete = null)
+ {
+ if (is_array($abstract)) {
+ foreach ($abstract as $key => $val) {
+ $this->bind($key, $val);
+ }
+ } elseif ($concrete instanceof Closure) {
+ $this->bind[$abstract] = $concrete;
+ } elseif (is_object($concrete)) {
+ $this->instance($abstract, $concrete);
+ } else {
+ $abstract = $this->getAlias($abstract);
+ if ($abstract != $concrete) {
+ $this->bind[$abstract] = $concrete;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 根据别名获取真实类名
+ * @param string $abstract
+ * @return string
+ */
+ public function getAlias(string $abstract): string
+ {
+ if (isset($this->bind[$abstract])) {
+ $bind = $this->bind[$abstract];
+
+ if (is_string($bind)) {
+ return $this->getAlias($bind);
+ }
+ }
+
+ return $abstract;
+ }
+
+ /**
+ * 绑定一个类实例到容器
+ * @access public
+ * @param string $abstract 类名或者标识
+ * @param object $instance 类的实例
+ * @return $this
+ */
+ public function instance(string $abstract, $instance)
+ {
+ $abstract = $this->getAlias($abstract);
+
+ $this->instances[$abstract] = $instance;
+
+ return $this;
+ }
+
+ /**
+ * 判断容器中是否存在类及标识
+ * @access public
+ * @param string $abstract 类名或者标识
+ * @return bool
+ */
+ public function bound(string $abstract): bool
+ {
+ return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
+ }
+
+ /**
+ * 判断容器中是否存在类及标识
+ * @access public
+ * @param string $name 类名或者标识
+ * @return bool
+ */
+ public function has($name): bool
+ {
+ return $this->bound($name);
+ }
+
+ /**
+ * 判断容器中是否存在对象实例
+ * @access public
+ * @param string $abstract 类名或者标识
+ * @return bool
+ */
+ public function exists(string $abstract): bool
+ {
+ $abstract = $this->getAlias($abstract);
+
+ return isset($this->instances[$abstract]);
+ }
+
+ /**
+ * 创建类的实例 已经存在则直接获取
+ * @access public
+ * @param string $abstract 类名或者标识
+ * @param array $vars 变量
+ * @param bool $newInstance 是否每次创建新的实例
+ * @return mixed
+ */
+ public function make(string $abstract, array $vars = [], bool $newInstance = false)
+ {
+ $abstract = $this->getAlias($abstract);
+
+ if (isset($this->instances[$abstract]) && !$newInstance) {
+ return $this->instances[$abstract];
+ }
+
+ if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
+ $object = $this->invokeFunction($this->bind[$abstract], $vars);
+ } else {
+ $object = $this->invokeClass($abstract, $vars);
+ }
+
+ if (!$newInstance) {
+ $this->instances[$abstract] = $object;
+ }
+
+ return $object;
+ }
+
+ /**
+ * 删除容器中的对象实例
+ * @access public
+ * @param string $name 类名或者标识
+ * @return void
+ */
+ public function delete($name)
+ {
+ $name = $this->getAlias($name);
+
+ if (isset($this->instances[$name])) {
+ unset($this->instances[$name]);
+ }
+ }
+
+ /**
+ * 执行函数或者闭包方法 支持参数调用
+ * @access public
+ * @param string|Closure $function 函数或者闭包
+ * @param array $vars 参数
+ * @return mixed
+ */
+ public function invokeFunction($function, array $vars = [])
+ {
+ try {
+ $reflect = new ReflectionFunction($function);
+ } catch (ReflectionException $e) {
+ throw new FuncNotFoundException("function not exists: {$function}()", $function, $e);
+ }
+
+ $args = $this->bindParams($reflect, $vars);
+
+ return $function(...$args);
+ }
+
+ /**
+ * 调用反射执行类的方法 支持参数绑定
+ * @access public
+ * @param mixed $method 方法
+ * @param array $vars 参数
+ * @param bool $accessible 设置是否可访问
+ * @return mixed
+ */
+ public function invokeMethod($method, array $vars = [], bool $accessible = false)
+ {
+ if (is_array($method)) {
+ [$class, $method] = $method;
+
+ $class = is_object($class) ? $class : $this->invokeClass($class);
+ } else {
+ // 静态方法
+ [$class, $method] = explode('::', $method);
+ }
+
+ try {
+ $reflect = new ReflectionMethod($class, $method);
+ } catch (ReflectionException $e) {
+ $class = is_object($class) ? get_class($class) : $class;
+ throw new FuncNotFoundException('method not exists: ' . $class . '::' . $method . '()', "{$class}::{$method}", $e);
+ }
+
+ $args = $this->bindParams($reflect, $vars);
+
+ if ($accessible) {
+ $reflect->setAccessible($accessible);
+ }
+
+ return $reflect->invokeArgs(is_object($class) ? $class : null, $args);
+ }
+
+ /**
+ * 调用反射执行类的方法 支持参数绑定
+ * @access public
+ * @param object $instance 对象实例
+ * @param mixed $reflect 反射类
+ * @param array $vars 参数
+ * @return mixed
+ */
+ public function invokeReflectMethod($instance, $reflect, array $vars = [])
+ {
+ $args = $this->bindParams($reflect, $vars);
+
+ return $reflect->invokeArgs($instance, $args);
+ }
+
+ /**
+ * 调用反射执行callable 支持参数绑定
+ * @access public
+ * @param mixed $callable
+ * @param array $vars 参数
+ * @param bool $accessible 设置是否可访问
+ * @return mixed
+ */
+ public function invoke($callable, array $vars = [], bool $accessible = false)
+ {
+ if ($callable instanceof Closure) {
+ return $this->invokeFunction($callable, $vars);
+ } elseif (is_string($callable) && false === strpos($callable, '::')) {
+ return $this->invokeFunction($callable, $vars);
+ } else {
+ return $this->invokeMethod($callable, $vars, $accessible);
+ }
+ }
+
+ /**
+ * 调用反射执行类的实例化 支持依赖注入
+ * @access public
+ * @param string $class 类名
+ * @param array $vars 参数
+ * @return mixed
+ */
+ public function invokeClass(string $class, array $vars = [])
+ {
+ try {
+ $reflect = new ReflectionClass($class);
+ } catch (ReflectionException $e) {
+ throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
+ }
+
+ if ($reflect->hasMethod('__make')) {
+ $method = $reflect->getMethod('__make');
+ if ($method->isPublic() && $method->isStatic()) {
+ $args = $this->bindParams($method, $vars);
+ $object = $method->invokeArgs(null, $args);
+ $this->invokeAfter($class, $object);
+ return $object;
+ }
+ }
+
+ $constructor = $reflect->getConstructor();
+
+ $args = $constructor ? $this->bindParams($constructor, $vars) : [];
+
+ $object = $reflect->newInstanceArgs($args);
+
+ $this->invokeAfter($class, $object);
+
+ return $object;
+ }
+
+ /**
+ * 执行invokeClass回调
+ * @access protected
+ * @param string $class 对象类名
+ * @param object $object 容器对象实例
+ * @return void
+ */
+ protected function invokeAfter(string $class, $object): void
+ {
+ if (isset($this->invokeCallback['*'])) {
+ foreach ($this->invokeCallback['*'] as $callback) {
+ $callback($object, $this);
+ }
+ }
+
+ if (isset($this->invokeCallback[$class])) {
+ foreach ($this->invokeCallback[$class] as $callback) {
+ $callback($object, $this);
+ }
+ }
+ }
+
+ /**
+ * 绑定参数
+ * @access protected
+ * @param ReflectionFunctionAbstract $reflect 反射类
+ * @param array $vars 参数
+ * @return array
+ */
+ protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array
+ {
+ if ($reflect->getNumberOfParameters() == 0) {
+ return [];
+ }
+
+ // 判断数组类型 数字数组时按顺序绑定参数
+ reset($vars);
+ $type = key($vars) === 0 ? 1 : 0;
+ $params = $reflect->getParameters();
+ $args = [];
+
+ foreach ($params as $param) {
+ $name = $param->getName();
+ $lowerName = Str::snake($name);
+ $reflectionType = $param->getType();
+
+ if ($reflectionType && $reflectionType->isBuiltin() === false) {
+ $args[] = $this->getObjectParam($reflectionType->getName(), $vars);
+ } elseif (1 == $type && !empty($vars)) {
+ $args[] = array_shift($vars);
+ } elseif (0 == $type && array_key_exists($name, $vars)) {
+ $args[] = $vars[$name];
+ } elseif (0 == $type && array_key_exists($lowerName, $vars)) {
+ $args[] = $vars[$lowerName];
+ } elseif ($param->isDefaultValueAvailable()) {
+ $args[] = $param->getDefaultValue();
+ } else {
+ throw new InvalidArgumentException('method param miss:' . $name);
+ }
+ }
+
+ return $args;
+ }
+
+ /**
+ * 创建工厂对象实例
+ * @param string $name 工厂类名
+ * @param string $namespace 默认命名空间
+ * @param array $args
+ * @return mixed
+ * @deprecated
+ * @access public
+ */
+ public static function factory(string $name, string $namespace = '', ...$args)
+ {
+ $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name);
+
+ return Container::getInstance()->invokeClass($class, $args);
+ }
+
+ /**
+ * 获取对象类型的参数值
+ * @access protected
+ * @param string $className 类名
+ * @param array $vars 参数
+ * @return mixed
+ */
+ protected function getObjectParam(string $className, array &$vars)
+ {
+ $array = $vars;
+ $value = array_shift($array);
+
+ if ($value instanceof $className) {
+ $result = $value;
+ array_shift($vars);
+ } else {
+ $result = $this->make($className);
+ }
+
+ return $result;
+ }
+
+ public function __set($name, $value)
+ {
+ $this->bind($name, $value);
+ }
+
+ public function __get($name)
+ {
+ return $this->get($name);
+ }
+
+ public function __isset($name): bool
+ {
+ return $this->exists($name);
+ }
+
+ public function __unset($name)
+ {
+ $this->delete($name);
+ }
+
+ public function offsetExists($key)
+ {
+ return $this->exists($key);
+ }
+
+ public function offsetGet($key)
+ {
+ return $this->make($key);
+ }
+
+ public function offsetSet($key, $value)
+ {
+ $this->bind($key, $value);
+ }
+
+ public function offsetUnset($key)
+ {
+ $this->delete($key);
+ }
+
+ //Countable
+ public function count()
+ {
+ return count($this->instances);
+ }
+
+ //IteratorAggregate
+ public function getIterator()
+ {
+ return new ArrayIterator($this->instances);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Cookie.php b/vendor/topthink/framework/src/think/Cookie.php
new file mode 100644
index 000000000..ebbfd64e0
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Cookie.php
@@ -0,0 +1,230 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use DateTimeInterface;
+
+/**
+ * Cookie管理类
+ * @package think
+ */
+class Cookie
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [
+ // cookie 保存时间
+ 'expire' => 0,
+ // cookie 保存路径
+ 'path' => '/',
+ // cookie 有效域名
+ 'domain' => '',
+ // cookie 启用安全传输
+ 'secure' => false,
+ // httponly设置
+ 'httponly' => false,
+ // samesite 设置,支持 'strict' 'lax'
+ 'samesite' => '',
+ ];
+
+ /**
+ * Cookie写入数据
+ * @var array
+ */
+ protected $cookie = [];
+
+ /**
+ * 当前Request对象
+ * @var Request
+ */
+ protected $request;
+
+ /**
+ * 构造方法
+ * @access public
+ */
+ public function __construct(Request $request, array $config = [])
+ {
+ $this->request = $request;
+ $this->config = array_merge($this->config, array_change_key_case($config));
+ }
+
+ public static function __make(Request $request, Config $config)
+ {
+ return new static($request, $config->get('cookie'));
+ }
+
+ /**
+ * 获取cookie
+ * @access public
+ * @param mixed $name 数据名称
+ * @param string $default 默认值
+ * @return mixed
+ */
+ public function get(string $name = '', $default = null)
+ {
+ return $this->request->cookie($name, $default);
+ }
+
+ /**
+ * 是否存在Cookie参数
+ * @access public
+ * @param string $name 变量名
+ * @return bool
+ */
+ public function has(string $name): bool
+ {
+ return $this->request->has($name, 'cookie');
+ }
+
+ /**
+ * Cookie 设置
+ *
+ * @access public
+ * @param string $name cookie名称
+ * @param string $value cookie值
+ * @param mixed $option 可选参数
+ * @return void
+ */
+ public function set(string $name, string $value, $option = null): void
+ {
+ // 参数设置(会覆盖黙认设置)
+ if (!is_null($option)) {
+ if (is_numeric($option) || $option instanceof DateTimeInterface) {
+ $option = ['expire' => $option];
+ }
+
+ $config = array_merge($this->config, array_change_key_case($option));
+ } else {
+ $config = $this->config;
+ }
+
+ if ($config['expire'] instanceof DateTimeInterface) {
+ $expire = $config['expire']->getTimestamp();
+ } else {
+ $expire = !empty($config['expire']) ? time() + intval($config['expire']) : 0;
+ }
+
+ $this->setCookie($name, $value, $expire, $config);
+ }
+
+ /**
+ * Cookie 保存
+ *
+ * @access public
+ * @param string $name cookie名称
+ * @param string $value cookie值
+ * @param int $expire 有效期
+ * @param array $option 可选参数
+ * @return void
+ */
+ protected function setCookie(string $name, string $value, int $expire, array $option = []): void
+ {
+ $this->cookie[$name] = [$value, $expire, $option];
+ }
+
+ /**
+ * 永久保存Cookie数据
+ * @access public
+ * @param string $name cookie名称
+ * @param string $value cookie值
+ * @param mixed $option 可选参数 可能会是 null|integer|string
+ * @return void
+ */
+ public function forever(string $name, string $value = '', $option = null): void
+ {
+ if (is_null($option) || is_numeric($option)) {
+ $option = [];
+ }
+
+ $option['expire'] = 315360000;
+
+ $this->set($name, $value, $option);
+ }
+
+ /**
+ * Cookie删除
+ * @access public
+ * @param string $name cookie名称
+ * @return void
+ */
+ public function delete(string $name): void
+ {
+ $this->setCookie($name, '', time() - 3600, $this->config);
+ }
+
+ /**
+ * 获取cookie保存数据
+ * @access public
+ * @return array
+ */
+ public function getCookie(): array
+ {
+ return $this->cookie;
+ }
+
+ /**
+ * 保存Cookie
+ * @access public
+ * @return void
+ */
+ public function save(): void
+ {
+ foreach ($this->cookie as $name => $val) {
+ [$value, $expire, $option] = $val;
+
+ $this->saveCookie(
+ $name,
+ $value,
+ $expire,
+ $option['path'],
+ $option['domain'],
+ $option['secure'] ? true : false,
+ $option['httponly'] ? true : false,
+ $option['samesite']
+ );
+ }
+ }
+
+ /**
+ * 保存Cookie
+ * @access public
+ * @param string $name cookie名称
+ * @param string $value cookie值
+ * @param int $expire cookie过期时间
+ * @param string $path 有效的服务器路径
+ * @param string $domain 有效域名/子域名
+ * @param bool $secure 是否仅仅通过HTTPS
+ * @param bool $httponly 仅可通过HTTP访问
+ * @param string $samesite 防止CSRF攻击和用户追踪
+ * @return void
+ */
+ protected function saveCookie(string $name, string $value, int $expire, string $path, string $domain, bool $secure, bool $httponly, string $samesite): void
+ {
+ if (version_compare(PHP_VERSION, '7.3.0', '>=')) {
+ setcookie($name, $value, [
+ 'expires' => $expire,
+ 'path' => $path,
+ 'domain' => $domain,
+ 'secure' => $secure,
+ 'httponly' => $httponly,
+ 'samesite' => $samesite,
+ ]);
+ } else {
+ setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
+ }
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/Db.php b/vendor/topthink/framework/src/think/Db.php
new file mode 100644
index 000000000..0048874fb
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Db.php
@@ -0,0 +1,117 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+/**
+ * 数据库管理类
+ * @package think
+ * @property Config $config
+ */
+class Db extends DbManager
+{
+ /**
+ * @param Event $event
+ * @param Config $config
+ * @param Log $log
+ * @param Cache $cache
+ * @return Db
+ * @codeCoverageIgnore
+ */
+ public static function __make(Event $event, Config $config, Log $log, Cache $cache)
+ {
+ $db = new static();
+ $db->setConfig($config);
+ $db->setEvent($event);
+ $db->setLog($log);
+
+ $store = $db->getConfig('cache_store');
+ $db->setCache($cache->store($store));
+ $db->triggerSql();
+
+ return $db;
+ }
+
+ /**
+ * 注入模型对象
+ * @access public
+ * @return void
+ */
+ protected function modelMaker()
+ {
+ }
+
+ /**
+ * 设置配置对象
+ * @access public
+ * @param Config $config 配置对象
+ * @return void
+ */
+ public function setConfig($config): void
+ {
+ $this->config = $config;
+ }
+
+ /**
+ * 获取配置参数
+ * @access public
+ * @param string $name 配置参数
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function getConfig(string $name = '', $default = null)
+ {
+ if ('' !== $name) {
+ return $this->config->get('database.' . $name, $default);
+ }
+
+ return $this->config->get('database', []);
+ }
+
+ /**
+ * 设置Event对象
+ * @param Event $event
+ */
+ public function setEvent(Event $event): void
+ {
+ $this->event = $event;
+ }
+
+ /**
+ * 注册回调方法
+ * @access public
+ * @param string $event 事件名
+ * @param callable $callback 回调方法
+ * @return void
+ */
+ public function event(string $event, callable $callback): void
+ {
+ if ($this->event) {
+ $this->event->listen('db.' . $event, $callback);
+ }
+ }
+
+ /**
+ * 触发事件
+ * @access public
+ * @param string $event 事件名
+ * @param mixed $params 传入参数
+ * @param bool $once
+ * @return mixed
+ */
+ public function trigger(string $event, $params = null, bool $once = false)
+ {
+ if ($this->event) {
+ return $this->event->trigger('db.' . $event, $params, $once);
+ }
+ }
+}
diff --git a/thinkphp/library/think/Env.php b/vendor/topthink/framework/src/think/Env.php
old mode 100755
new mode 100644
similarity index 51%
rename from thinkphp/library/think/Env.php
rename to vendor/topthink/framework/src/think/Env.php
index eaeee943e..4c26b33a9
--- a/thinkphp/library/think/Env.php
+++ b/vendor/topthink/framework/src/think/Env.php
@@ -2,16 +2,23 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think;
-class Env
+use ArrayAccess;
+
+/**
+ * Env管理类
+ * @package think
+ */
+class Env implements ArrayAccess
{
/**
* 环境变量数据
@@ -27,23 +34,23 @@ class Env
/**
* 读取环境变量定义文件
* @access public
- * @param string $file 环境变量定义文件
+ * @param string $file 环境变量定义文件
* @return void
*/
- public function load($file)
+ public function load(string $file): void
{
- $env = parse_ini_file($file, true);
+ $env = parse_ini_file($file, true) ?: [];
$this->set($env);
}
/**
* 获取环境变量值
* @access public
- * @param string $name 环境变量名
- * @param mixed $default 默认值
+ * @param string $name 环境变量名
+ * @param mixed $default 默认值
* @return mixed
*/
- public function get($name = null, $default = null, $php_prefix = true)
+ public function get(string $name = null, $default = null)
{
if (is_null($name)) {
return $this->data;
@@ -55,16 +62,12 @@ class Env
return $this->data[$name];
}
- return $this->getEnv($name, $default, $php_prefix);
+ return $this->getEnv($name, $default);
}
- protected function getEnv($name, $default = null, $php_prefix = true)
+ protected function getEnv(string $name, $default = null)
{
- if ($php_prefix) {
- $name = 'PHP_' . $name;
- }
-
- $result = getenv($name);
+ $result = getenv('PHP_' . $name);
if (false === $result) {
return $default;
@@ -86,11 +89,11 @@ class Env
/**
* 设置环境变量值
* @access public
- * @param string|array $env 环境变量
- * @param mixed $value 值
+ * @param string|array $env 环境变量
+ * @param mixed $value 值
* @return void
*/
- public function set($env, $value = null)
+ public function set($env, $value = null): void
{
if (is_array($env)) {
$env = array_change_key_case($env, CASE_UPPER);
@@ -110,4 +113,69 @@ class Env
$this->data[$name] = $value;
}
}
+
+ /**
+ * 检测是否存在环境变量
+ * @access public
+ * @param string $name 参数名
+ * @return bool
+ */
+ public function has(string $name): bool
+ {
+ return !is_null($this->get($name));
+ }
+
+ /**
+ * 设置环境变量
+ * @access public
+ * @param string $name 参数名
+ * @param mixed $value 值
+ */
+ public function __set(string $name, $value): void
+ {
+ $this->set($name, $value);
+ }
+
+ /**
+ * 获取环境变量
+ * @access public
+ * @param string $name 参数名
+ * @return mixed
+ */
+ public function __get(string $name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * 检测是否存在环境变量
+ * @access public
+ * @param string $name 参数名
+ * @return bool
+ */
+ public function __isset(string $name): bool
+ {
+ return $this->has($name);
+ }
+
+ // ArrayAccess
+ public function offsetSet($name, $value): void
+ {
+ $this->set($name, $value);
+ }
+
+ public function offsetExists($name): bool
+ {
+ return $this->__isset($name);
+ }
+
+ public function offsetUnset($name)
+ {
+ throw new Exception('not support: unset');
+ }
+
+ public function offsetGet($name)
+ {
+ return $this->get($name);
+ }
}
diff --git a/vendor/topthink/framework/src/think/Event.php b/vendor/topthink/framework/src/think/Event.php
new file mode 100644
index 000000000..6a0eb1f0e
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Event.php
@@ -0,0 +1,263 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use ReflectionClass;
+use ReflectionMethod;
+
+/**
+ * 事件管理类
+ * @package think
+ */
+class Event
+{
+ /**
+ * 监听者
+ * @var array
+ */
+ protected $listener = [];
+
+ /**
+ * 事件别名
+ * @var array
+ */
+ protected $bind = [
+ 'AppInit' => event\AppInit::class,
+ 'HttpRun' => event\HttpRun::class,
+ 'HttpEnd' => event\HttpEnd::class,
+ 'RouteLoaded' => event\RouteLoaded::class,
+ 'LogWrite' => event\LogWrite::class,
+ ];
+
+ /**
+ * 应用对象
+ * @var App
+ */
+ protected $app;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * 批量注册事件监听
+ * @access public
+ * @param array $events 事件定义
+ * @return $this
+ */
+ public function listenEvents(array $events)
+ {
+ foreach ($events as $event => $listeners) {
+ if (isset($this->bind[$event])) {
+ $event = $this->bind[$event];
+ }
+
+ $this->listener[$event] = array_merge($this->listener[$event] ?? [], $listeners);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 注册事件监听
+ * @access public
+ * @param string $event 事件名称
+ * @param mixed $listener 监听操作(或者类名)
+ * @param bool $first 是否优先执行
+ * @return $this
+ */
+ public function listen(string $event, $listener, bool $first = false)
+ {
+ if (isset($this->bind[$event])) {
+ $event = $this->bind[$event];
+ }
+
+ if ($first && isset($this->listener[$event])) {
+ array_unshift($this->listener[$event], $listener);
+ } else {
+ $this->listener[$event][] = $listener;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 是否存在事件监听
+ * @access public
+ * @param string $event 事件名称
+ * @return bool
+ */
+ public function hasListener(string $event): bool
+ {
+ if (isset($this->bind[$event])) {
+ $event = $this->bind[$event];
+ }
+
+ return isset($this->listener[$event]);
+ }
+
+ /**
+ * 移除事件监听
+ * @access public
+ * @param string $event 事件名称
+ * @return void
+ */
+ public function remove(string $event): void
+ {
+ if (isset($this->bind[$event])) {
+ $event = $this->bind[$event];
+ }
+
+ unset($this->listener[$event]);
+ }
+
+ /**
+ * 指定事件别名标识 便于调用
+ * @access public
+ * @param array $events 事件别名
+ * @return $this
+ */
+ public function bind(array $events)
+ {
+ $this->bind = array_merge($this->bind, $events);
+
+ return $this;
+ }
+
+ /**
+ * 注册事件订阅者
+ * @access public
+ * @param mixed $subscriber 订阅者
+ * @return $this
+ */
+ public function subscribe($subscriber)
+ {
+ $subscribers = (array) $subscriber;
+
+ foreach ($subscribers as $subscriber) {
+ if (is_string($subscriber)) {
+ $subscriber = $this->app->make($subscriber);
+ }
+
+ if (method_exists($subscriber, 'subscribe')) {
+ // 手动订阅
+ $subscriber->subscribe($this);
+ } else {
+ // 智能订阅
+ $this->observe($subscriber);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 自动注册事件观察者
+ * @access public
+ * @param string|object $observer 观察者
+ * @param null|string $prefix 事件名前缀
+ * @return $this
+ */
+ public function observe($observer, string $prefix = '')
+ {
+ if (is_string($observer)) {
+ $observer = $this->app->make($observer);
+ }
+
+ $reflect = new ReflectionClass($observer);
+ $methods = $reflect->getMethods(ReflectionMethod::IS_PUBLIC);
+
+ if (empty($prefix) && $reflect->hasProperty('eventPrefix')) {
+ $reflectProperty = $reflect->getProperty('eventPrefix');
+ $reflectProperty->setAccessible(true);
+ $prefix = $reflectProperty->getValue($observer);
+ }
+
+ foreach ($methods as $method) {
+ $name = $method->getName();
+ if (0 === strpos($name, 'on')) {
+ $this->listen($prefix . substr($name, 2), [$observer, $name]);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 触发事件
+ * @access public
+ * @param string|object $event 事件名称
+ * @param mixed $params 传入参数
+ * @param bool $once 只获取一个有效返回值
+ * @return mixed
+ */
+ public function trigger($event, $params = null, bool $once = false)
+ {
+ if (is_object($event)) {
+ $params = $event;
+ $event = get_class($event);
+ }
+
+ if (isset($this->bind[$event])) {
+ $event = $this->bind[$event];
+ }
+
+ $result = [];
+ $listeners = $this->listener[$event] ?? [];
+ $listeners = array_unique($listeners, SORT_REGULAR);
+
+ foreach ($listeners as $key => $listener) {
+ $result[$key] = $this->dispatch($listener, $params);
+
+ if (false === $result[$key] || (!is_null($result[$key]) && $once)) {
+ break;
+ }
+ }
+
+ return $once ? end($result) : $result;
+ }
+
+ /**
+ * 触发事件(只获取一个有效返回值)
+ * @param $event
+ * @param null $params
+ * @return mixed
+ */
+ public function until($event, $params = null)
+ {
+ return $this->trigger($event, $params, true);
+ }
+
+ /**
+ * 执行事件调度
+ * @access protected
+ * @param mixed $event 事件方法
+ * @param mixed $params 参数
+ * @return mixed
+ */
+ protected function dispatch($event, $params = null)
+ {
+ if (!is_string($event)) {
+ $call = $event;
+ } elseif (strpos($event, '::')) {
+ $call = $event;
+ } else {
+ $obj = $this->app->make($event);
+ $call = [$obj, 'handle'];
+ }
+
+ return $this->app->invoke($call, [$params]);
+ }
+
+}
diff --git a/thinkphp/library/think/Exception.php b/vendor/topthink/framework/src/think/Exception.php
old mode 100755
new mode 100644
similarity index 88%
rename from thinkphp/library/think/Exception.php
rename to vendor/topthink/framework/src/think/Exception.php
index 414a090ad..5cf79548a
--- a/thinkphp/library/think/Exception.php
+++ b/vendor/topthink/framework/src/think/Exception.php
@@ -2,18 +2,22 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think;
+/**
+ * 异常基础类
+ * @package think
+ */
class Exception extends \Exception
{
-
/**
* 保存异常页面显示的额外Debug数据
* @var array
@@ -37,7 +41,7 @@ class Exception extends \Exception
* @param string $label 数据分类,用于异常页面显示
* @param array $data 需要显示的数据,必须为关联数组
*/
- final protected function setData($label, array $data)
+ final protected function setData(string $label, array $data)
{
$this->data[$label] = $data;
}
diff --git a/thinkphp/library/think/Facade.php b/vendor/topthink/framework/src/think/Facade.php
old mode 100755
new mode 100644
similarity index 59%
rename from thinkphp/library/think/Facade.php
rename to vendor/topthink/framework/src/think/Facade.php
index ac5ae28bc..9a0e33394
--- a/thinkphp/library/think/Facade.php
+++ b/vendor/topthink/framework/src/think/Facade.php
@@ -2,60 +2,35 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
-
namespace think;
+/**
+ * Facade管理类
+ */
class Facade
{
- /**
- * 绑定对象
- * @var array
- */
- protected static $bind = [];
-
/**
* 始终创建新的对象实例
* @var bool
*/
protected static $alwaysNewInstance;
- /**
- * 绑定类的静态代理
- * @static
- * @access public
- * @param string|array $name 类标识
- * @param string $class 类名
- * @return object
- */
- public static function bind($name, $class = null)
- {
- if (__CLASS__ != static::class) {
- return self::__callStatic('bind', func_get_args());
- }
-
- if (is_array($name)) {
- self::$bind = array_merge(self::$bind, $name);
- } else {
- self::$bind[$name] = $class;
- }
- }
-
/**
* 创建Facade实例
* @static
* @access protected
- * @param string $class 类名或标识
- * @param array $args 变量
- * @param bool $newInstance 是否每次创建新的实例
+ * @param string $class 类名或标识
+ * @param array $args 变量
+ * @param bool $newInstance 是否每次创建新的实例
* @return object
*/
- protected static function createFacade($class = '', $args = [], $newInstance = false)
+ protected static function createFacade(string $class = '', array $args = [], bool $newInstance = false)
{
$class = $class ?: static::class;
@@ -63,8 +38,6 @@ class Facade
if ($facadeClass) {
$class = $facadeClass;
- } elseif (isset(self::$bind[$class])) {
- $class = self::$bind[$class];
}
if (static::$alwaysNewInstance) {
@@ -75,7 +48,7 @@ class Facade
}
/**
- * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * 获取当前Facade对应类名
* @access protected
* @return string
*/
@@ -85,7 +58,7 @@ class Facade
/**
* 带参数实例化当前Facade类
* @access public
- * @return mixed
+ * @return object
*/
public static function instance(...$args)
{
@@ -97,12 +70,12 @@ class Facade
/**
* 调用类的实例
* @access public
- * @param string $class 类名或者标识
- * @param array|true $args 变量
- * @param bool $newInstance 是否每次创建新的实例
- * @return mixed
+ * @param string $class 类名或者标识
+ * @param array|true $args 变量
+ * @param bool $newInstance 是否每次创建新的实例
+ * @return object
*/
- public static function make($class, $args = [], $newInstance = false)
+ public static function make(string $class, $args = [], $newInstance = false)
{
if (__CLASS__ != static::class) {
return self::__callStatic('make', func_get_args());
diff --git a/vendor/topthink/framework/src/think/File.php b/vendor/topthink/framework/src/think/File.php
new file mode 100644
index 000000000..f7c37bdbe
--- /dev/null
+++ b/vendor/topthink/framework/src/think/File.php
@@ -0,0 +1,187 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use SplFileInfo;
+use think\exception\FileException;
+
+/**
+ * 文件上传类
+ * @package think
+ */
+class File extends SplFileInfo
+{
+
+ /**
+ * 文件hash规则
+ * @var array
+ */
+ protected $hash = [];
+
+ protected $hashName;
+
+ public function __construct(string $path, bool $checkPath = true)
+ {
+ if ($checkPath && !is_file($path)) {
+ throw new FileException(sprintf('The file "%s" does not exist', $path));
+ }
+
+ parent::__construct($path);
+ }
+
+ /**
+ * 获取文件的哈希散列值
+ * @access public
+ * @param string $type
+ * @return string
+ */
+ public function hash(string $type = 'sha1'): string
+ {
+ if (!isset($this->hash[$type])) {
+ $this->hash[$type] = hash_file($type, $this->getPathname());
+ }
+
+ return $this->hash[$type];
+ }
+
+ /**
+ * 获取文件的MD5值
+ * @access public
+ * @return string
+ */
+ public function md5(): string
+ {
+ return $this->hash('md5');
+ }
+
+ /**
+ * 获取文件的SHA1值
+ * @access public
+ * @return string
+ */
+ public function sha1(): string
+ {
+ return $this->hash('sha1');
+ }
+
+ /**
+ * 获取文件类型信息
+ * @access public
+ * @return string
+ */
+ public function getMime(): string
+ {
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
+
+ return finfo_file($finfo, $this->getPathname());
+ }
+
+ /**
+ * 移动文件
+ * @access public
+ * @param string $directory 保存路径
+ * @param string|null $name 保存的文件名
+ * @return File
+ */
+ public function move(string $directory, string $name = null): File
+ {
+ $target = $this->getTargetFile($directory, $name);
+
+ set_error_handler(function ($type, $msg) use (&$error) {
+ $error = $msg;
+ });
+ $renamed = rename($this->getPathname(), (string) $target);
+ restore_error_handler();
+ if (!$renamed) {
+ throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
+ }
+
+ @chmod((string) $target, 0666 & ~umask());
+
+ return $target;
+ }
+
+ /**
+ * 实例化一个新文件
+ * @param string $directory
+ * @param null|string $name
+ * @return File
+ */
+ protected function getTargetFile(string $directory, string $name = null): File
+ {
+ if (!is_dir($directory)) {
+ if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
+ throw new FileException(sprintf('Unable to create the "%s" directory', $directory));
+ }
+ } elseif (!is_writable($directory)) {
+ throw new FileException(sprintf('Unable to write in the "%s" directory', $directory));
+ }
+
+ $target = rtrim($directory, '/\\') . \DIRECTORY_SEPARATOR . (null === $name ? $this->getBasename() : $this->getName($name));
+
+ return new self($target, false);
+ }
+
+ /**
+ * 获取文件名
+ * @param string $name
+ * @return string
+ */
+ protected function getName(string $name): string
+ {
+ $originalName = str_replace('\\', '/', $name);
+ $pos = strrpos($originalName, '/');
+ $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1);
+
+ return $originalName;
+ }
+
+ /**
+ * 文件扩展名
+ * @return string
+ */
+ public function extension(): string
+ {
+ return $this->getExtension();
+ }
+
+ /**
+ * 自动生成文件名
+ * @access public
+ * @param string|\Closure $rule
+ * @return string
+ */
+ public function hashName($rule = ''): string
+ {
+ if (!$this->hashName) {
+ if ($rule instanceof \Closure) {
+ $this->hashName = call_user_func_array($rule, [$this]);
+ } else {
+ switch (true) {
+ case in_array($rule, hash_algos()):
+ $hash = $this->hash($rule);
+ $this->hashName = substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2);
+ break;
+ case is_callable($rule):
+ $this->hashName = call_user_func($rule);
+ break;
+ default:
+ $this->hashName = date('Ymd') . DIRECTORY_SEPARATOR . md5((string) microtime(true));
+ break;
+ }
+ }
+ }
+
+ return $this->hashName . '.' . $this->extension();
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Filesystem.php b/vendor/topthink/framework/src/think/Filesystem.php
new file mode 100644
index 000000000..0aee929f5
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Filesystem.php
@@ -0,0 +1,89 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use InvalidArgumentException;
+use think\filesystem\Driver;
+use think\filesystem\driver\Local;
+use think\helper\Arr;
+
+/**
+ * Class Filesystem
+ * @package think
+ * @mixin Driver
+ * @mixin Local
+ */
+class Filesystem extends Manager
+{
+ protected $namespace = '\\think\\filesystem\\driver\\';
+
+ /**
+ * @param null|string $name
+ * @return Driver
+ */
+ public function disk(string $name = null): Driver
+ {
+ return $this->driver($name);
+ }
+
+ protected function resolveType(string $name)
+ {
+ return $this->getDiskConfig($name, 'type', 'local');
+ }
+
+ protected function resolveConfig(string $name)
+ {
+ return $this->getDiskConfig($name);
+ }
+
+ /**
+ * 获取缓存配置
+ * @access public
+ * @param null|string $name 名称
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function getConfig(string $name = null, $default = null)
+ {
+ if (!is_null($name)) {
+ return $this->app->config->get('filesystem.' . $name, $default);
+ }
+
+ return $this->app->config->get('filesystem');
+ }
+
+ /**
+ * 获取磁盘配置
+ * @param string $disk
+ * @param null $name
+ * @param null $default
+ * @return array
+ */
+ public function getDiskConfig($disk, $name = null, $default = null)
+ {
+ if ($config = $this->getConfig("disks.{$disk}")) {
+ return Arr::get($config, $name, $default);
+ }
+
+ throw new InvalidArgumentException("Disk [$disk] not found.");
+ }
+
+ /**
+ * 默认驱动
+ * @return string|null
+ */
+ public function getDefaultDriver()
+ {
+ return $this->getConfig('default');
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Http.php b/vendor/topthink/framework/src/think/Http.php
new file mode 100644
index 000000000..4e49c88cb
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Http.php
@@ -0,0 +1,288 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use think\event\HttpEnd;
+use think\event\HttpRun;
+use think\event\RouteLoaded;
+use think\exception\Handle;
+use Throwable;
+
+/**
+ * Web应用管理类
+ * @package think
+ */
+class Http
+{
+
+ /**
+ * @var App
+ */
+ protected $app;
+
+ /**
+ * 应用名称
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * 应用路径
+ * @var string
+ */
+ protected $path;
+
+ /**
+ * 路由路径
+ * @var string
+ */
+ protected $routePath;
+
+ /**
+ * 是否绑定应用
+ * @var bool
+ */
+ protected $isBind = false;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+
+ $this->routePath = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR;
+ }
+
+ /**
+ * 设置应用名称
+ * @access public
+ * @param string $name 应用名称
+ * @return $this
+ */
+ public function name(string $name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * 获取应用名称
+ * @access public
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name ?: '';
+ }
+
+ /**
+ * 设置应用目录
+ * @access public
+ * @param string $path 应用目录
+ * @return $this
+ */
+ public function path(string $path)
+ {
+ if (substr($path, -1) != DIRECTORY_SEPARATOR) {
+ $path .= DIRECTORY_SEPARATOR;
+ }
+
+ $this->path = $path;
+ return $this;
+ }
+
+ /**
+ * 获取应用路径
+ * @access public
+ * @return string
+ */
+ public function getPath(): string
+ {
+ return $this->path ?: '';
+ }
+
+ /**
+ * 获取路由目录
+ * @access public
+ * @return string
+ */
+ public function getRoutePath(): string
+ {
+ return $this->routePath;
+ }
+
+ /**
+ * 设置路由目录
+ * @access public
+ * @param string $path 路由定义目录
+ */
+ public function setRoutePath(string $path): void
+ {
+ $this->routePath = $path;
+ }
+
+ /**
+ * 设置应用绑定
+ * @access public
+ * @param bool $bind 是否绑定
+ * @return $this
+ */
+ public function setBind(bool $bind = true)
+ {
+ $this->isBind = $bind;
+ return $this;
+ }
+
+ /**
+ * 是否绑定应用
+ * @access public
+ * @return bool
+ */
+ public function isBind(): bool
+ {
+ return $this->isBind;
+ }
+
+ /**
+ * 执行应用程序
+ * @access public
+ * @param Request|null $request
+ * @return Response
+ */
+ public function run(Request $request = null): Response
+ {
+ //初始化
+ $this->initialize();
+
+ //自动创建request对象
+ $request = $request ?? $this->app->make('request', [], true);
+ $this->app->instance('request', $request);
+
+ try {
+ $response = $this->runWithRequest($request);
+ } catch (Throwable $e) {
+ $this->reportException($e);
+
+ $response = $this->renderException($request, $e);
+ }
+
+ return $response;
+ }
+
+ /**
+ * 初始化
+ */
+ protected function initialize()
+ {
+ if (!$this->app->initialized()) {
+ $this->app->initialize();
+ }
+ }
+
+ /**
+ * 执行应用程序
+ * @param Request $request
+ * @return mixed
+ */
+ protected function runWithRequest(Request $request)
+ {
+ // 加载全局中间件
+ $this->loadMiddleware();
+
+ // 监听HttpRun
+ $this->app->event->trigger(HttpRun::class);
+
+ return $this->app->middleware->pipeline()
+ ->send($request)
+ ->then(function ($request) {
+ return $this->dispatchToRoute($request);
+ });
+ }
+
+ protected function dispatchToRoute($request)
+ {
+ $withRoute = $this->app->config->get('app.with_route', true) ? function () {
+ $this->loadRoutes();
+ } : null;
+
+ return $this->app->route->dispatch($request, $withRoute);
+ }
+
+ /**
+ * 加载全局中间件
+ */
+ protected function loadMiddleware(): void
+ {
+ if (is_file($this->app->getBasePath() . 'middleware.php')) {
+ $this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php');
+ }
+ }
+
+ /**
+ * 加载路由
+ * @access protected
+ * @return void
+ */
+ protected function loadRoutes(): void
+ {
+ // 加载路由定义
+ $routePath = $this->getRoutePath();
+
+ if (is_dir($routePath)) {
+ $files = glob($routePath . '*.php');
+ foreach ($files as $file) {
+ include $file;
+ }
+ }
+
+ $this->app->event->trigger(RouteLoaded::class);
+ }
+
+ /**
+ * Report the exception to the exception handler.
+ *
+ * @param Throwable $e
+ * @return void
+ */
+ protected function reportException(Throwable $e)
+ {
+ $this->app->make(Handle::class)->report($e);
+ }
+
+ /**
+ * Render the exception to a response.
+ *
+ * @param Request $request
+ * @param Throwable $e
+ * @return Response
+ */
+ protected function renderException($request, Throwable $e)
+ {
+ return $this->app->make(Handle::class)->render($request, $e);
+ }
+
+ /**
+ * HttpEnd
+ * @param Response $response
+ * @return void
+ */
+ public function end(Response $response): void
+ {
+ $this->app->event->trigger(HttpEnd::class, $response);
+
+ //执行中间件
+ $this->app->middleware->end($response);
+
+ // 写入日志
+ $this->app->log->save();
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/Lang.php b/vendor/topthink/framework/src/think/Lang.php
new file mode 100644
index 000000000..0b79b7604
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Lang.php
@@ -0,0 +1,294 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+/**
+ * 多语言管理类
+ * @package think
+ */
+class Lang
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [
+ // 默认语言
+ 'default_lang' => 'zh-cn',
+ // 允许的语言列表
+ 'allow_lang_list' => [],
+ // 是否使用Cookie记录
+ 'use_cookie' => true,
+ // 扩展语言包
+ 'extend_list' => [],
+ // 多语言cookie变量
+ 'cookie_var' => 'think_lang',
+ // 多语言header变量
+ 'header_var' => 'think-lang',
+ // 多语言自动侦测变量名
+ 'detect_var' => 'lang',
+ // Accept-Language转义为对应语言包名称
+ 'accept_language' => [
+ 'zh-hans-cn' => 'zh-cn',
+ ],
+ // 是否支持语言分组
+ 'allow_group' => false,
+ ];
+
+ /**
+ * 多语言信息
+ * @var array
+ */
+ private $lang = [];
+
+ /**
+ * 当前语言
+ * @var string
+ */
+ private $range = 'zh-cn';
+
+ /**
+ * 构造方法
+ * @access public
+ * @param array $config
+ */
+ public function __construct(array $config = [])
+ {
+ $this->config = array_merge($this->config, array_change_key_case($config));
+ $this->range = $this->config['default_lang'];
+ }
+
+ public static function __make(Config $config)
+ {
+ return new static($config->get('lang'));
+ }
+
+ /**
+ * 设置当前语言
+ * @access public
+ * @param string $lang 语言
+ * @return void
+ */
+ public function setLangSet(string $lang): void
+ {
+ $this->range = $lang;
+ }
+
+ /**
+ * 获取当前语言
+ * @access public
+ * @return string
+ */
+ public function getLangSet(): string
+ {
+ return $this->range;
+ }
+
+ /**
+ * 获取默认语言
+ * @access public
+ * @return string
+ */
+ public function defaultLangSet()
+ {
+ return $this->config['default_lang'];
+ }
+
+ /**
+ * 加载语言定义(不区分大小写)
+ * @access public
+ * @param string|array $file 语言文件
+ * @param string $range 语言作用域
+ * @return array
+ */
+ public function load($file, $range = ''): array
+ {
+ $range = $range ?: $this->range;
+ if (!isset($this->lang[$range])) {
+ $this->lang[$range] = [];
+ }
+
+ $lang = [];
+
+ foreach ((array) $file as $name) {
+ if (is_file($name)) {
+ $result = $this->parse($name);
+ $lang = array_change_key_case($result) + $lang;
+ }
+ }
+
+ if (!empty($lang)) {
+ $this->lang[$range] = $lang + $this->lang[$range];
+ }
+
+ return $this->lang[$range];
+ }
+
+ /**
+ * 解析语言文件
+ * @access protected
+ * @param string $file 语言文件名
+ * @return array
+ */
+ protected function parse(string $file): array
+ {
+ $type = pathinfo($file, PATHINFO_EXTENSION);
+
+ switch ($type) {
+ case 'php':
+ $result = include $file;
+ break;
+ case 'yml':
+ case 'yaml':
+ if (function_exists('yaml_parse_file')) {
+ $result = yaml_parse_file($file);
+ }
+ break;
+ case 'json':
+ $data = file_get_contents($file);
+
+ if (false !== $data) {
+ $data = json_decode($data, true);
+
+ if (json_last_error() === JSON_ERROR_NONE) {
+ $result = $data;
+ }
+ }
+
+ break;
+ }
+
+ return isset($result) && is_array($result) ? $result : [];
+ }
+
+ /**
+ * 判断是否存在语言定义(不区分大小写)
+ * @access public
+ * @param string|null $name 语言变量
+ * @param string $range 语言作用域
+ * @return bool
+ */
+ public function has(string $name, string $range = ''): bool
+ {
+ $range = $range ?: $this->range;
+
+ if ($this->config['allow_group'] && strpos($name, '.')) {
+ [$name1, $name2] = explode('.', $name, 2);
+ return isset($this->lang[$range][strtolower($name1)][$name2]);
+ }
+
+ return isset($this->lang[$range][strtolower($name)]);
+ }
+
+ /**
+ * 获取语言定义(不区分大小写)
+ * @access public
+ * @param string|null $name 语言变量
+ * @param array $vars 变量替换
+ * @param string $range 语言作用域
+ * @return mixed
+ */
+ public function get(string $name = null, array $vars = [], string $range = '')
+ {
+ $range = $range ?: $this->range;
+
+ // 空参数返回所有定义
+ if (is_null($name)) {
+ return $this->lang[$range] ?? [];
+ }
+
+ if ($this->config['allow_group'] && strpos($name, '.')) {
+ [$name1, $name2] = explode('.', $name, 2);
+
+ $value = $this->lang[$range][strtolower($name1)][$name2] ?? $name;
+ } else {
+ $value = $this->lang[$range][strtolower($name)] ?? $name;
+ }
+
+ // 变量解析
+ if (!empty($vars) && is_array($vars)) {
+ /**
+ * Notes:
+ * 为了检测的方便,数字索引的判断仅仅是参数数组的第一个元素的key为数字0
+ * 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数
+ */
+ if (key($vars) === 0) {
+ // 数字索引解析
+ array_unshift($vars, $value);
+ $value = call_user_func_array('sprintf', $vars);
+ } else {
+ // 关联索引解析
+ $replace = array_keys($vars);
+ foreach ($replace as &$v) {
+ $v = "{:{$v}}";
+ }
+ $value = str_replace($replace, $vars, $value);
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * 自动侦测设置获取语言选择
+ * @access public
+ * @param Request $request
+ * @return string
+ */
+ public function detect(Request $request): string
+ {
+ // 自动侦测设置获取语言选择
+ $langSet = '';
+
+ if ($request->get($this->config['detect_var'])) {
+ // url中设置了语言变量
+ $langSet = strtolower($request->get($this->config['detect_var']));
+ } elseif ($request->header($this->config['header_var'])) {
+ // Header中设置了语言变量
+ $langSet = strtolower($request->header($this->config['header_var']));
+ } elseif ($request->cookie($this->config['cookie_var'])) {
+ // Cookie中设置了语言变量
+ $langSet = strtolower($request->cookie($this->config['cookie_var']));
+ } elseif ($request->server('HTTP_ACCEPT_LANGUAGE')) {
+ // 自动侦测浏览器语言
+ $match = preg_match('/^([a-z\d\-]+)/i', $request->server('HTTP_ACCEPT_LANGUAGE'), $matches);
+ if ($match) {
+ $langSet = strtolower($matches[1]);
+ if (isset($this->config['accept_language'][$langSet])) {
+ $langSet = $this->config['accept_language'][$langSet];
+ }
+ }
+ }
+
+ if (empty($this->config['allow_lang_list']) || in_array($langSet, $this->config['allow_lang_list'])) {
+ // 合法的语言
+ $this->range = $langSet;
+ }
+
+ return $this->range;
+ }
+
+ /**
+ * 保存当前语言到Cookie
+ * @access public
+ * @param Cookie $cookie Cookie对象
+ * @return void
+ */
+ public function saveToCookie(Cookie $cookie)
+ {
+ if ($this->config['use_cookie']) {
+ $cookie->set($this->config['cookie_var'], $this->range);
+ }
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/Log.php b/vendor/topthink/framework/src/think/Log.php
new file mode 100644
index 000000000..c31210ce4
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Log.php
@@ -0,0 +1,342 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use InvalidArgumentException;
+use Psr\Log\LoggerInterface;
+use think\event\LogWrite;
+use think\helper\Arr;
+use think\log\Channel;
+use think\log\ChannelSet;
+
+/**
+ * 日志管理类
+ * @package think
+ * @mixin Channel
+ */
+class Log extends Manager implements LoggerInterface
+{
+ const EMERGENCY = 'emergency';
+ const ALERT = 'alert';
+ const CRITICAL = 'critical';
+ const ERROR = 'error';
+ const WARNING = 'warning';
+ const NOTICE = 'notice';
+ const INFO = 'info';
+ const DEBUG = 'debug';
+ const SQL = 'sql';
+
+ protected $namespace = '\\think\\log\\driver\\';
+
+ /**
+ * 默认驱动
+ * @return string|null
+ */
+ public function getDefaultDriver()
+ {
+ return $this->getConfig('default');
+ }
+
+ /**
+ * 获取日志配置
+ * @access public
+ * @param null|string $name 名称
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function getConfig(string $name = null, $default = null)
+ {
+ if (!is_null($name)) {
+ return $this->app->config->get('log.' . $name, $default);
+ }
+
+ return $this->app->config->get('log');
+ }
+
+ /**
+ * 获取渠道配置
+ * @param string $channel
+ * @param null $name
+ * @param null $default
+ * @return array
+ */
+ public function getChannelConfig($channel, $name = null, $default = null)
+ {
+ if ($config = $this->getConfig("channels.{$channel}")) {
+ return Arr::get($config, $name, $default);
+ }
+
+ throw new InvalidArgumentException("Channel [$channel] not found.");
+ }
+
+ /**
+ * driver()的别名
+ * @param string|array $name 渠道名
+ * @return Channel|ChannelSet
+ */
+ public function channel($name = null)
+ {
+ if (is_array($name)) {
+ return new ChannelSet($this, $name);
+ }
+
+ return $this->driver($name);
+ }
+
+ protected function resolveType(string $name)
+ {
+ return $this->getChannelConfig($name, 'type', 'file');
+ }
+
+ public function createDriver(string $name)
+ {
+ $driver = parent::createDriver($name);
+
+ $lazy = !$this->getChannelConfig($name, "realtime_write", false) && !$this->app->runningInConsole();
+ $allow = array_merge($this->getConfig("level", []), $this->getChannelConfig($name, "level", []));
+
+ return new Channel($name, $driver, $allow, $lazy, $this->app->event);
+ }
+
+ protected function resolveConfig(string $name)
+ {
+ return $this->getChannelConfig($name);
+ }
+
+ /**
+ * 清空日志信息
+ * @access public
+ * @param string|array $channel 日志通道名
+ * @return $this
+ */
+ public function clear($channel = '*')
+ {
+ if ('*' == $channel) {
+ $channel = array_keys($this->drivers);
+ }
+
+ $this->channel($channel)->clear();
+
+ return $this;
+ }
+
+ /**
+ * 关闭本次请求日志写入
+ * @access public
+ * @param string|array $channel 日志通道名
+ * @return $this
+ */
+ public function close($channel = '*')
+ {
+ if ('*' == $channel) {
+ $channel = array_keys($this->drivers);
+ }
+
+ $this->channel($channel)->close();
+
+ return $this;
+ }
+
+ /**
+ * 获取日志信息
+ * @access public
+ * @param string $channel 日志通道名
+ * @return array
+ */
+ public function getLog(string $channel = null): array
+ {
+ return $this->channel($channel)->getLog();
+ }
+
+ /**
+ * 保存日志信息
+ * @access public
+ * @return bool
+ */
+ public function save(): bool
+ {
+ /** @var Channel $channel */
+ foreach ($this->drivers as $channel) {
+ $channel->save();
+ }
+
+ return true;
+ }
+
+ /**
+ * 记录日志信息
+ * @access public
+ * @param mixed $msg 日志信息
+ * @param string $type 日志级别
+ * @param array $context 替换内容
+ * @param bool $lazy
+ * @return $this
+ */
+ public function record($msg, string $type = 'info', array $context = [], bool $lazy = true)
+ {
+ $channel = $this->getConfig('type_channel.' . $type);
+
+ $this->channel($channel)->record($msg, $type, $context, $lazy);
+
+ return $this;
+ }
+
+ /**
+ * 实时写入日志信息
+ * @access public
+ * @param mixed $msg 调试信息
+ * @param string $type 日志级别
+ * @param array $context 替换内容
+ * @return $this
+ */
+ public function write($msg, string $type = 'info', array $context = [])
+ {
+ return $this->record($msg, $type, $context, false);
+ }
+
+ /**
+ * 注册日志写入事件监听
+ * @param $listener
+ * @return Event
+ */
+ public function listen($listener)
+ {
+ return $this->app->event->listen(LogWrite::class, $listener);
+ }
+
+ /**
+ * 记录日志信息
+ * @access public
+ * @param string $level 日志级别
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function log($level, $message, array $context = []): void
+ {
+ $this->record($message, $level, $context);
+ }
+
+ /**
+ * 记录emergency信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function emergency($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录警报信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function alert($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录紧急情况
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function critical($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录错误信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function error($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录warning信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function warning($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录notice信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function notice($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录一般信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function info($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录调试信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function debug($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录sql信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function sql($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ public function __call($method, $parameters)
+ {
+ $this->log($method, ...$parameters);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Manager.php b/vendor/topthink/framework/src/think/Manager.php
new file mode 100644
index 000000000..ca3f6a5a9
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Manager.php
@@ -0,0 +1,177 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use InvalidArgumentException;
+use think\helper\Str;
+
+abstract class Manager
+{
+ /** @var App */
+ protected $app;
+
+ /**
+ * 驱动
+ * @var array
+ */
+ protected $drivers = [];
+
+ /**
+ * 驱动的命名空间
+ * @var string
+ */
+ protected $namespace = null;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * 获取驱动实例
+ * @param null|string $name
+ * @return mixed
+ */
+ protected function driver(string $name = null)
+ {
+ $name = $name ?: $this->getDefaultDriver();
+
+ if (is_null($name)) {
+ throw new InvalidArgumentException(sprintf(
+ 'Unable to resolve NULL driver for [%s].',
+ static::class
+ ));
+ }
+
+ return $this->drivers[$name] = $this->getDriver($name);
+ }
+
+ /**
+ * 获取驱动实例
+ * @param string $name
+ * @return mixed
+ */
+ protected function getDriver(string $name)
+ {
+ return $this->drivers[$name] ?? $this->createDriver($name);
+ }
+
+ /**
+ * 获取驱动类型
+ * @param string $name
+ * @return mixed
+ */
+ protected function resolveType(string $name)
+ {
+ return $name;
+ }
+
+ /**
+ * 获取驱动配置
+ * @param string $name
+ * @return mixed
+ */
+ protected function resolveConfig(string $name)
+ {
+ return $name;
+ }
+
+ /**
+ * 获取驱动类
+ * @param string $type
+ * @return string
+ */
+ protected function resolveClass(string $type): string
+ {
+ if ($this->namespace || false !== strpos($type, '\\')) {
+ $class = false !== strpos($type, '\\') ? $type : $this->namespace . Str::studly($type);
+
+ if (class_exists($class)) {
+ return $class;
+ }
+ }
+
+ throw new InvalidArgumentException("Driver [$type] not supported.");
+ }
+
+ /**
+ * 获取驱动参数
+ * @param $name
+ * @return array
+ */
+ protected function resolveParams($name): array
+ {
+ $config = $this->resolveConfig($name);
+ return [$config];
+ }
+
+ /**
+ * 创建驱动
+ *
+ * @param string $name
+ * @return mixed
+ *
+ */
+ protected function createDriver(string $name)
+ {
+ $type = $this->resolveType($name);
+
+ $method = 'create' . Str::studly($type) . 'Driver';
+
+ $params = $this->resolveParams($name);
+
+ if (method_exists($this, $method)) {
+ return $this->$method(...$params);
+ }
+
+ $class = $this->resolveClass($type);
+
+ return $this->app->invokeClass($class, $params);
+ }
+
+ /**
+ * 移除一个驱动实例
+ *
+ * @param array|string|null $name
+ * @return $this
+ */
+ public function forgetDriver($name = null)
+ {
+ $name = $name ?? $this->getDefaultDriver();
+
+ foreach ((array) $name as $cacheName) {
+ if (isset($this->drivers[$cacheName])) {
+ unset($this->drivers[$cacheName]);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 默认驱动
+ * @return string|null
+ */
+ abstract public function getDefaultDriver();
+
+ /**
+ * 动态调用
+ * @param string $method
+ * @param array $parameters
+ * @return mixed
+ */
+ public function __call($method, $parameters)
+ {
+ return $this->driver()->$method(...$parameters);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Middleware.php b/vendor/topthink/framework/src/think/Middleware.php
new file mode 100644
index 000000000..a3db0f2f3
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Middleware.php
@@ -0,0 +1,257 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Closure;
+use InvalidArgumentException;
+use LogicException;
+use think\exception\Handle;
+use Throwable;
+
+/**
+ * 中间件管理类
+ * @package think
+ */
+class Middleware
+{
+ /**
+ * 中间件执行队列
+ * @var array
+ */
+ protected $queue = [];
+
+ /**
+ * 应用对象
+ * @var App
+ */
+ protected $app;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * 导入中间件
+ * @access public
+ * @param array $middlewares
+ * @param string $type 中间件类型
+ * @return void
+ */
+ public function import(array $middlewares = [], string $type = 'global'): void
+ {
+ foreach ($middlewares as $middleware) {
+ $this->add($middleware, $type);
+ }
+ }
+
+ /**
+ * 注册中间件
+ * @access public
+ * @param mixed $middleware
+ * @param string $type 中间件类型
+ * @return void
+ */
+ public function add($middleware, string $type = 'global'): void
+ {
+ $middleware = $this->buildMiddleware($middleware, $type);
+
+ if (!empty($middleware)) {
+ $this->queue[$type][] = $middleware;
+ $this->queue[$type] = array_unique($this->queue[$type], SORT_REGULAR);
+ }
+ }
+
+ /**
+ * 注册路由中间件
+ * @access public
+ * @param mixed $middleware
+ * @return void
+ */
+ public function route($middleware): void
+ {
+ $this->add($middleware, 'route');
+ }
+
+ /**
+ * 注册控制器中间件
+ * @access public
+ * @param mixed $middleware
+ * @return void
+ */
+ public function controller($middleware): void
+ {
+ $this->add($middleware, 'controller');
+ }
+
+ /**
+ * 注册中间件到开始位置
+ * @access public
+ * @param mixed $middleware
+ * @param string $type 中间件类型
+ */
+ public function unshift($middleware, string $type = 'global')
+ {
+ $middleware = $this->buildMiddleware($middleware, $type);
+
+ if (!empty($middleware)) {
+ if (!isset($this->queue[$type])) {
+ $this->queue[$type] = [];
+ }
+
+ array_unshift($this->queue[$type], $middleware);
+ }
+ }
+
+ /**
+ * 获取注册的中间件
+ * @access public
+ * @param string $type 中间件类型
+ * @return array
+ */
+ public function all(string $type = 'global'): array
+ {
+ return $this->queue[$type] ?? [];
+ }
+
+ /**
+ * 调度管道
+ * @access public
+ * @param string $type 中间件类型
+ * @return Pipeline
+ */
+ public function pipeline(string $type = 'global')
+ {
+ return (new Pipeline())
+ ->through(array_map(function ($middleware) {
+ return function ($request, $next) use ($middleware) {
+ [$call, $params] = $middleware;
+ if (is_array($call) && is_string($call[0])) {
+ $call = [$this->app->make($call[0]), $call[1]];
+ }
+ $response = call_user_func($call, $request, $next, ...$params);
+
+ if (!$response instanceof Response) {
+ throw new LogicException('The middleware must return Response instance');
+ }
+ return $response;
+ };
+ }, $this->sortMiddleware($this->queue[$type] ?? [])))
+ ->whenException([$this, 'handleException']);
+ }
+
+ /**
+ * 结束调度
+ * @param Response $response
+ */
+ public function end(Response $response)
+ {
+ foreach ($this->queue as $queue) {
+ foreach ($queue as $middleware) {
+ [$call] = $middleware;
+ if (is_array($call) && is_string($call[0])) {
+ $instance = $this->app->make($call[0]);
+ if (method_exists($instance, 'end')) {
+ $instance->end($response);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 异常处理
+ * @param Request $passable
+ * @param Throwable $e
+ * @return Response
+ */
+ public function handleException($passable, Throwable $e)
+ {
+ /** @var Handle $handler */
+ $handler = $this->app->make(Handle::class);
+
+ $handler->report($e);
+
+ return $handler->render($passable, $e);
+ }
+
+ /**
+ * 解析中间件
+ * @access protected
+ * @param mixed $middleware
+ * @param string $type 中间件类型
+ * @return array
+ */
+ protected function buildMiddleware($middleware, string $type): array
+ {
+ if (is_array($middleware)) {
+ [$middleware, $params] = $middleware;
+ }
+
+ if ($middleware instanceof Closure) {
+ return [$middleware, $params ?? []];
+ }
+
+ if (!is_string($middleware)) {
+ throw new InvalidArgumentException('The middleware is invalid');
+ }
+
+ //中间件别名检查
+ $alias = $this->app->config->get('middleware.alias', []);
+
+ if (isset($alias[$middleware])) {
+ $middleware = $alias[$middleware];
+ }
+
+ if (is_array($middleware)) {
+ $this->import($middleware, $type);
+ return [];
+ }
+
+ return [[$middleware, 'handle'], $params ?? []];
+ }
+
+ /**
+ * 中间件排序
+ * @param array $middlewares
+ * @return array
+ */
+ protected function sortMiddleware(array $middlewares)
+ {
+ $priority = $this->app->config->get('middleware.priority', []);
+ uasort($middlewares, function ($a, $b) use ($priority) {
+ $aPriority = $this->getMiddlewarePriority($priority, $a);
+ $bPriority = $this->getMiddlewarePriority($priority, $b);
+ return $bPriority - $aPriority;
+ });
+
+ return $middlewares;
+ }
+
+ /**
+ * 获取中间件优先级
+ * @param $priority
+ * @param $middleware
+ * @return int
+ */
+ protected function getMiddlewarePriority($priority, $middleware)
+ {
+ [$call] = $middleware;
+ if (is_array($call) && is_string($call[0])) {
+ $index = array_search($call[0], array_reverse($priority));
+ return false === $index ? -1 : $index;
+ }
+ return -1;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/Pipeline.php b/vendor/topthink/framework/src/think/Pipeline.php
new file mode 100644
index 000000000..77151f3ac
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Pipeline.php
@@ -0,0 +1,107 @@
+
+// +----------------------------------------------------------------------
+namespace think;
+
+use Closure;
+use Exception;
+use Throwable;
+
+class Pipeline
+{
+ protected $passable;
+
+ protected $pipes = [];
+
+ protected $exceptionHandler;
+
+ /**
+ * 初始数据
+ * @param $passable
+ * @return $this
+ */
+ public function send($passable)
+ {
+ $this->passable = $passable;
+ return $this;
+ }
+
+ /**
+ * 调用栈
+ * @param $pipes
+ * @return $this
+ */
+ public function through($pipes)
+ {
+ $this->pipes = is_array($pipes) ? $pipes : func_get_args();
+ return $this;
+ }
+
+ /**
+ * 执行
+ * @param Closure $destination
+ * @return mixed
+ */
+ public function then(Closure $destination)
+ {
+ $pipeline = array_reduce(
+ array_reverse($this->pipes),
+ $this->carry(),
+ function ($passable) use ($destination) {
+ try {
+ return $destination($passable);
+ } catch (Throwable | Exception $e) {
+ return $this->handleException($passable, $e);
+ }
+ }
+ );
+
+ return $pipeline($this->passable);
+ }
+
+ /**
+ * 设置异常处理器
+ * @param callable $handler
+ * @return $this
+ */
+ public function whenException($handler)
+ {
+ $this->exceptionHandler = $handler;
+ return $this;
+ }
+
+ protected function carry()
+ {
+ return function ($stack, $pipe) {
+ return function ($passable) use ($stack, $pipe) {
+ try {
+ return $pipe($passable, $stack);
+ } catch (Throwable | Exception $e) {
+ return $this->handleException($passable, $e);
+ }
+ };
+ };
+ }
+
+ /**
+ * 异常处理
+ * @param $passable
+ * @param $e
+ * @return mixed
+ */
+ protected function handleException($passable, Throwable $e)
+ {
+ if ($this->exceptionHandler) {
+ return call_user_func($this->exceptionHandler, $passable, $e);
+ }
+ throw $e;
+ }
+
+}
diff --git a/thinkphp/library/think/Request.php b/vendor/topthink/framework/src/think/Request.php
old mode 100755
new mode 100644
similarity index 55%
rename from thinkphp/library/think/Request.php
rename to vendor/topthink/framework/src/think/Request.php
index 92a401d73..a21976df1
--- a/thinkphp/library/think/Request.php
+++ b/vendor/topthink/framework/src/think/Request.php
@@ -2,46 +2,79 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think;
-use think\facade\Cookie;
-use think\facade\Session;
+use ArrayAccess;
+use think\file\UploadedFile;
+use think\route\Rule;
-class Request
+/**
+ * 请求管理类
+ * @package think
+ */
+class Request implements ArrayAccess
{
/**
- * 配置参数
+ * 兼容PATH_INFO获取
* @var array
*/
- protected $config = [
- // 表单请求类型伪装变量
- 'var_method' => '_method',
- // 表单ajax伪装变量
- 'var_ajax' => '_ajax',
- // 表单pjax伪装变量
- 'var_pjax' => '_pjax',
- // PATHINFO变量名 用于兼容模式
- 'var_pathinfo' => 's',
- // 兼容PATH_INFO获取
- 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
- // 默认全局过滤方法 用逗号分隔多个
- 'default_filter' => '',
- // 域名根,如thinkphp.cn
- 'url_domain_root' => '',
- // HTTPS代理标识
- 'https_agent_name' => '',
- // IP代理获取标识
- 'http_agent_ip' => 'HTTP_X_REAL_IP',
- // URL伪静态后缀
- 'url_html_suffix' => 'html',
- ];
+ protected $pathinfoFetch = ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'];
+
+ /**
+ * PATHINFO变量名 用于兼容模式
+ * @var string
+ */
+ protected $varPathinfo = 's';
+
+ /**
+ * 请求类型
+ * @var string
+ */
+ protected $varMethod = '_method';
+
+ /**
+ * 表单ajax伪装变量
+ * @var string
+ */
+ protected $varAjax = '_ajax';
+
+ /**
+ * 表单pjax伪装变量
+ * @var string
+ */
+ protected $varPjax = '_pjax';
+
+ /**
+ * 域名根
+ * @var string
+ */
+ protected $rootDomain = '';
+
+ /**
+ * HTTPS代理标识
+ * @var string
+ */
+ protected $httpsAgentName = '';
+
+ /**
+ * 前端代理服务器IP
+ * @var array
+ */
+ protected $proxyServerIp = [];
+
+ /**
+ * 前端代理服务器真实IP头
+ * @var array
+ */
+ protected $proxyServerIpHeader = ['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP'];
/**
* 请求类型
@@ -49,18 +82,18 @@ class Request
*/
protected $method;
- /**
- * 主机名(含端口)
- * @var string
- */
- protected $host;
-
/**
* 域名(含协议及端口)
* @var string
*/
protected $domain;
+ /**
+ * HOST(含端口)
+ * @var string
+ */
+ protected $host;
+
/**
* 子域名
* @var string
@@ -110,22 +143,10 @@ class Request
protected $path;
/**
- * 当前路由信息
- * @var array
- */
- protected $routeInfo = [];
-
- /**
- * 当前调度信息
- * @var \think\route\Dispatch
- */
- protected $dispatch;
-
- /**
- * 当前模块名
+ * 当前请求的IP地址
* @var string
*/
- protected $module;
+ protected $realIP;
/**
* 当前控制器名
@@ -139,12 +160,6 @@ class Request
*/
protected $action;
- /**
- * 当前语言集
- * @var string
- */
- protected $langset;
-
/**
* 当前请求参数
* @var array
@@ -169,12 +184,24 @@ class Request
*/
protected $request = [];
+ /**
+ * 当前路由对象
+ * @var Rule
+ */
+ protected $rule;
+
/**
* 当前ROUTE参数
* @var array
*/
protected $route = [];
+ /**
+ * 中间件传递的参数
+ * @var array
+ */
+ protected $middleware = [];
+
/**
* 当前PUT参数
* @var array
@@ -182,23 +209,23 @@ class Request
protected $put;
/**
- * 当前SESSION参数
- * @var array
+ * SESSION对象
+ * @var Session
*/
- protected $session = [];
+ protected $session;
/**
- * 当前FILE参数
- * @var array
- */
- protected $file = [];
-
- /**
- * 当前COOKIE参数
+ * COOKIE数据
* @var array
*/
protected $cookie = [];
+ /**
+ * ENV对象
+ * @var Env
+ */
+ protected $env;
+
/**
* 当前SERVER参数
* @var array
@@ -206,10 +233,10 @@ class Request
protected $server = [];
/**
- * 当前ENV参数
+ * 当前FILE参数
* @var array
*/
- protected $env = [];
+ protected $file = [];
/**
* 当前HEADER参数
@@ -248,30 +275,13 @@ class Request
*/
protected $filter;
- /**
- * 扩展方法
- * @var array
- */
- protected $hook = [];
-
/**
* php://input内容
* @var string
*/
+ // php://input
protected $input;
- /**
- * 请求缓存
- * @var array
- */
- protected $cache;
-
- /**
- * 缓存是否检查
- * @var bool
- */
- protected $isCheckCache;
-
/**
* 请求安全Key
* @var string
@@ -287,172 +297,71 @@ class Request
/**
* 架构函数
* @access public
- * @param array $options 参数
*/
- public function __construct(array $options = [])
+ public function __construct()
{
- $this->init($options);
-
// 保存 php://input
$this->input = file_get_contents('php://input');
}
- public function init(array $options = [])
+ public static function __make(App $app)
{
- $this->config = array_merge($this->config, $options);
-
- if (is_null($this->filter) && !empty($this->config['default_filter'])) {
- $this->filter = $this->config['default_filter'];
- }
- }
-
- public function config($name = null)
- {
- if (is_null($name)) {
- return $this->config;
- }
- return isset($this->config[$name]) ? $this->config[$name] : null;
- }
-
- public static function __make(App $app, Config $config)
- {
- $request = new static($config->pull('app'));
-
- $request->server = $_SERVER;
- $request->env = $app['env']->get();
-
- return $request;
- }
-
- public function __call($method, $args)
- {
- if (array_key_exists($method, $this->hook)) {
- array_unshift($args, $this);
- return call_user_func_array($this->hook[$method], $args);
- }
-
- throw new Exception('method not exists:' . static::class . '->' . $method);
- }
-
- /**
- * Hook 方法注入
- * @access public
- * @param string|array $method 方法名
- * @param mixed $callback callable
- * @return void
- */
- public function hook($method, $callback = null)
- {
- if (is_array($method)) {
- $this->hook = array_merge($this->hook, $method);
- } else {
- $this->hook[$method] = $callback;
- }
- }
-
- /**
- * 创建一个URL请求
- * @access public
- * @param string $uri URL地址
- * @param string $method 请求类型
- * @param array $params 请求参数
- * @param array $cookie
- * @param array $files
- * @param array $server
- * @param string $content
- * @return \think\Request
- */
- public function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null)
- {
- $server['PATH_INFO'] = '';
- $server['REQUEST_METHOD'] = strtoupper($method);
- $info = parse_url($uri);
-
- if (isset($info['host'])) {
- $server['SERVER_NAME'] = $info['host'];
- $server['HTTP_HOST'] = $info['host'];
- }
-
- if (isset($info['scheme'])) {
- if ('https' === $info['scheme']) {
- $server['HTTPS'] = 'on';
- $server['SERVER_PORT'] = 443;
- } else {
- unset($server['HTTPS']);
- $server['SERVER_PORT'] = 80;
- }
- }
-
- if (isset($info['port'])) {
- $server['SERVER_PORT'] = $info['port'];
- $server['HTTP_HOST'] = $server['HTTP_HOST'] . ':' . $info['port'];
- }
-
- if (isset($info['user'])) {
- $server['PHP_AUTH_USER'] = $info['user'];
- }
-
- if (isset($info['pass'])) {
- $server['PHP_AUTH_PW'] = $info['pass'];
- }
-
- if (!isset($info['path'])) {
- $info['path'] = '/';
- }
-
- $options = [];
- $queryString = '';
-
- $options[strtolower($method)] = $params;
-
- if (isset($info['query'])) {
- parse_str(html_entity_decode($info['query']), $query);
- if (!empty($params)) {
- $params = array_replace($query, $params);
- $queryString = http_build_query($params, '', '&');
- } else {
- $params = $query;
- $queryString = $info['query'];
- }
- } elseif (!empty($params)) {
- $queryString = http_build_query($params, '', '&');
- }
-
- if ($queryString) {
- parse_str($queryString, $get);
- $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get;
- }
-
- $server['REQUEST_URI'] = $info['path'] . ('' !== $queryString ? '?' . $queryString : '');
- $server['QUERY_STRING'] = $queryString;
- $options['cookie'] = $cookie;
- $options['param'] = $params;
- $options['file'] = $files;
- $options['server'] = $server;
- $options['url'] = $server['REQUEST_URI'];
- $options['baseUrl'] = $info['path'];
- $options['pathinfo'] = '/' == $info['path'] ? '/' : ltrim($info['path'], '/');
- $options['method'] = $server['REQUEST_METHOD'];
- $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : '';
- $options['content'] = $content;
-
$request = new static();
- foreach ($options as $name => $item) {
- if (property_exists($request, $name)) {
- $request->$name = $item;
+
+ if (function_exists('apache_request_headers') && $result = apache_request_headers()) {
+ $header = $result;
+ } else {
+ $header = [];
+ $server = $_SERVER;
+ foreach ($server as $key => $val) {
+ if (0 === strpos($key, 'HTTP_')) {
+ $key = str_replace('_', '-', strtolower(substr($key, 5)));
+ $header[$key] = $val;
+ }
+ }
+ if (isset($server['CONTENT_TYPE'])) {
+ $header['content-type'] = $server['CONTENT_TYPE'];
+ }
+ if (isset($server['CONTENT_LENGTH'])) {
+ $header['content-length'] = $server['CONTENT_LENGTH'];
}
}
+ $request->header = array_change_key_case($header);
+ $request->server = $_SERVER;
+ $request->env = $app->env;
+
+ $inputData = $request->getInputData($request->input);
+
+ $request->get = $_GET;
+ $request->post = $_POST ?: $inputData;
+ $request->put = $inputData;
+ $request->request = $_REQUEST;
+ $request->cookie = $_COOKIE;
+ $request->file = $_FILES ?? [];
+
return $request;
}
/**
- * 获取当前包含协议、端口的域名
+ * 设置当前包含协议的域名
+ * @access public
+ * @param string $domain 域名
+ * @return $this
+ */
+ public function setDomain(string $domain)
+ {
+ $this->domain = $domain;
+ return $this;
+ }
+
+ /**
+ * 获取当前包含协议的域名
* @access public
* @param bool $port 是否需要去除端口号
* @return string
*/
- public function domain($port = false)
+ public function domain(bool $port = false): string
{
return $this->scheme() . '://' . $this->host($port);
}
@@ -462,12 +371,12 @@ class Request
* @access public
* @return string
*/
- public function rootDomain()
+ public function rootDomain(): string
{
- $root = $this->config['url_domain_root'];
+ $root = $this->rootDomain;
if (!$root) {
- $item = explode('.', $this->host(true));
+ $item = explode('.', $this->host());
$count = count($item);
$root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0];
}
@@ -475,25 +384,34 @@ class Request
return $root;
}
+ /**
+ * 设置当前泛域名的值
+ * @access public
+ * @param string $domain 域名
+ * @return $this
+ */
+ public function setSubDomain(string $domain)
+ {
+ $this->subDomain = $domain;
+ return $this;
+ }
+
/**
* 获取当前子域名
* @access public
* @return string
*/
- public function subDomain()
+ public function subDomain(): string
{
if (is_null($this->subDomain)) {
// 获取当前主域名
- $rootDomain = $this->config['url_domain_root'];
+ $rootDomain = $this->rootDomain();
if ($rootDomain) {
- // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置
- $domain = explode('.', rtrim(stristr($this->host(true), $rootDomain, true), '.'));
+ $this->subDomain = rtrim(stristr($this->host(), $rootDomain, true), '.');
} else {
- $domain = explode('.', $this->host(true), -2);
+ $this->subDomain = '';
}
-
- $this->subDomain = implode('.', $domain);
}
return $this->subDomain;
@@ -505,7 +423,7 @@ class Request
* @param string $domain 域名
* @return $this
*/
- public function setPanDomain($domain)
+ public function setPanDomain(string $domain)
{
$this->panDomain = $domain;
return $this;
@@ -516,18 +434,18 @@ class Request
* @access public
* @return string
*/
- public function panDomain()
+ public function panDomain(): string
{
- return $this->panDomain;
+ return $this->panDomain ?: '';
}
/**
* 设置当前完整URL 包括QUERY_STRING
* @access public
- * @param string $url URL
+ * @param string $url URL地址
* @return $this
*/
- public function setUrl($url)
+ public function setUrl(string $url)
{
$this->url = $url;
return $this;
@@ -536,35 +454,35 @@ class Request
/**
* 获取当前完整URL 包括QUERY_STRING
* @access public
- * @param bool $complete 是否包含域名
+ * @param bool $complete 是否包含完整域名
* @return string
*/
- public function url($complete = false)
+ public function url(bool $complete = false): string
{
- if (!$this->url) {
- if ($this->isCli()) {
- $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
- } elseif ($this->server('HTTP_X_REWRITE_URL')) {
- $this->url = $this->server('HTTP_X_REWRITE_URL');
- } elseif ($this->server('REQUEST_URI')) {
- $this->url = $this->server('REQUEST_URI');
- } elseif ($this->server('ORIG_PATH_INFO')) {
- $this->url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : '');
- } else {
- $this->url = '';
- }
+ if ($this->url) {
+ $url = $this->url;
+ } elseif ($this->server('HTTP_X_REWRITE_URL')) {
+ $url = $this->server('HTTP_X_REWRITE_URL');
+ } elseif ($this->server('REQUEST_URI')) {
+ $url = $this->server('REQUEST_URI');
+ } elseif ($this->server('ORIG_PATH_INFO')) {
+ $url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : '');
+ } elseif (isset($_SERVER['argv'][1])) {
+ $url = $_SERVER['argv'][1];
+ } else {
+ $url = '';
}
- return $complete ? $this->domain() . $this->url : $this->url;
+ return $complete ? $this->domain() . $url : $url;
}
/**
- * 设置当前完整URL 不包括QUERY_STRING
+ * 设置当前URL 不含QUERY_STRING
* @access public
- * @param string $url URL
+ * @param string $url URL地址
* @return $this
*/
- public function setBaseUrl($url)
+ public function setBaseUrl(string $url)
{
$this->baseUrl = $url;
return $this;
@@ -573,26 +491,26 @@ class Request
/**
* 获取当前URL 不含QUERY_STRING
* @access public
- * @param bool $domain 是否包含域名
- * @return string|$this
+ * @param bool $complete 是否包含完整域名
+ * @return string
*/
- public function baseUrl($domain = false)
+ public function baseUrl(bool $complete = false): string
{
if (!$this->baseUrl) {
$str = $this->url();
$this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str;
}
- return $domain ? $this->domain() . $this->baseUrl : $this->baseUrl;
+ return $complete ? $this->domain() . $this->baseUrl : $this->baseUrl;
}
/**
- * 设置或获取当前执行的文件 SCRIPT_NAME
+ * 获取当前执行的文件 SCRIPT_NAME
* @access public
- * @param bool $domain 是否包含域名
- * @return string|$this
+ * @param bool $complete 是否包含完整域名
+ * @return string
*/
- public function baseFile($domain = false)
+ public function baseFile(bool $complete = false): string
{
if (!$this->baseFile) {
$url = '';
@@ -613,16 +531,16 @@ class Request
$this->baseFile = $url;
}
- return $domain ? $this->domain() . $this->baseFile : $this->baseFile;
+ return $complete ? $this->domain() . $this->baseFile : $this->baseFile;
}
/**
* 设置URL访问根地址
* @access public
* @param string $url URL地址
- * @return string|$this
+ * @return $this
*/
- public function setRoot($url = null)
+ public function setRoot(string $url)
{
$this->root = $url;
return $this;
@@ -631,10 +549,10 @@ class Request
/**
* 获取URL访问根地址
* @access public
- * @param bool $domain 是否包含域名
- * @return string|$this
+ * @param bool $complete 是否包含完整域名
+ * @return string
*/
- public function root($domain = false)
+ public function root(bool $complete = false): string
{
if (!$this->root) {
$file = $this->baseFile();
@@ -644,7 +562,7 @@ class Request
$this->root = rtrim($file, '/');
}
- return $domain ? $this->domain() . $this->root : $this->root;
+ return $complete ? $this->domain() . $this->root : $this->root;
}
/**
@@ -652,7 +570,7 @@ class Request
* @access public
* @return string
*/
- public function rootUrl()
+ public function rootUrl(): string
{
$base = $this->root();
$root = strpos($base, '.') ? ltrim(dirname($base), DIRECTORY_SEPARATOR) : $base;
@@ -664,7 +582,13 @@ class Request
return $root;
}
- public function setPathinfo($pathinfo)
+ /**
+ * 设置当前请求的pathinfo
+ * @access public
+ * @param string $pathinfo
+ * @return $this
+ */
+ public function setPathinfo(string $pathinfo)
{
$this->pathinfo = $pathinfo;
return $this;
@@ -675,26 +599,23 @@ class Request
* @access public
* @return string
*/
- public function pathinfo()
+ public function pathinfo(): string
{
if (is_null($this->pathinfo)) {
- if (isset($_GET[$this->config['var_pathinfo']])) {
+ if (isset($_GET[$this->varPathinfo])) {
// 判断URL里面是否有兼容模式参数
- $pathinfo = $_GET[$this->config['var_pathinfo']];
- unset($_GET[$this->config['var_pathinfo']]);
- unset($this->get[$this->config['var_pathinfo']]);
- } elseif ($this->isCli()) {
- // CLI模式下 index.php module/controller/action/params/...
- $pathinfo = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
- } elseif ('cli-server' == PHP_SAPI) {
- $pathinfo = strpos($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI');
+ $pathinfo = $_GET[$this->varPathinfo];
+ unset($_GET[$this->varPathinfo]);
+ unset($this->get[$this->varPathinfo]);
} elseif ($this->server('PATH_INFO')) {
$pathinfo = $this->server('PATH_INFO');
+ } elseif (false !== strpos(PHP_SAPI, 'cli')) {
+ $pathinfo = strpos($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI');
}
// 分析PATHINFO信息
if (!isset($pathinfo)) {
- foreach ($this->config['pathinfo_fetch'] as $type) {
+ foreach ($this->pathinfoFetch as $type) {
if ($this->server($type)) {
$pathinfo = (0 === strpos($this->server($type), $this->server('SCRIPT_NAME'))) ?
substr($this->server($type), strlen($this->server('SCRIPT_NAME'))) : $this->server($type);
@@ -713,38 +634,12 @@ class Request
return $this->pathinfo;
}
- /**
- * 获取当前请求URL的pathinfo信息(不含URL后缀)
- * @access public
- * @return string
- */
- public function path()
- {
- if (is_null($this->path)) {
- $suffix = $this->config['url_html_suffix'];
- $pathinfo = $this->pathinfo();
-
- if (false === $suffix) {
- // 禁止伪静态访问
- $this->path = $pathinfo;
- } elseif ($suffix) {
- // 去除正常的URL后缀
- $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);
- } else {
- // 允许任何后缀访问
- $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo);
- }
- }
-
- return $this->path;
- }
-
/**
* 当前URL的访问后缀
* @access public
* @return string
*/
- public function ext()
+ public function ext(): string
{
return pathinfo($this->pathinfo(), PATHINFO_EXTENSION);
}
@@ -755,7 +650,7 @@ class Request
* @param bool $float 是否使用浮点类型
* @return integer|float
*/
- public function time($float = false)
+ public function time(bool $float = false)
{
return $float ? $this->server('REQUEST_TIME_FLOAT') : $this->server('REQUEST_TIME');
}
@@ -763,14 +658,14 @@ class Request
/**
* 当前请求的资源类型
* @access public
- * @return false|string
+ * @return string
*/
- public function type()
+ public function type(): string
{
$accept = $this->server('HTTP_ACCEPT');
if (empty($accept)) {
- return false;
+ return '';
}
foreach ($this->mimeType as $key => $val) {
@@ -782,17 +677,17 @@ class Request
}
}
- return false;
+ return '';
}
/**
* 设置资源类型
* @access public
- * @param string|array $type 资源类型名
- * @param string $val 资源类型
+ * @param string|array $type 资源类型名
+ * @param string $val 资源类型
* @return void
*/
- public function mimeType($type, $val = '')
+ public function mimeType($type, $val = ''): void
{
if (is_array($type)) {
$this->mimeType = array_merge($this->mimeType, $type);
@@ -801,27 +696,39 @@ class Request
}
}
+ /**
+ * 设置请求类型
+ * @access public
+ * @param string $method 请求类型
+ * @return $this
+ */
+ public function setMethod(string $method)
+ {
+ $this->method = strtoupper($method);
+ return $this;
+ }
+
/**
* 当前的请求类型
* @access public
- * @param bool $origin 是否获取原始请求类型
+ * @param bool $origin 是否获取原始请求类型
* @return string
*/
- public function method($origin = false)
+ public function method(bool $origin = false): string
{
if ($origin) {
// 获取原始请求类型
return $this->server('REQUEST_METHOD') ?: 'GET';
} elseif (!$this->method) {
- if (isset($_POST[$this->config['var_method']])) {
- $method = strtolower($_POST[$this->config['var_method']]);
+ if (isset($this->post[$this->varMethod])) {
+ $method = strtolower($this->post[$this->varMethod]);
if (in_array($method, ['get', 'post', 'put', 'patch', 'delete'])) {
$this->method = strtoupper($method);
- $this->{$method} = $_POST;
+ $this->{$method} = $this->post;
} else {
$this->method = 'POST';
}
- unset($_POST[$this->config['var_method']]);
+ unset($this->post[$this->varMethod]);
} elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) {
$this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE'));
} else {
@@ -837,7 +744,7 @@ class Request
* @access public
* @return bool
*/
- public function isGet()
+ public function isGet(): bool
{
return $this->method() == 'GET';
}
@@ -847,7 +754,7 @@ class Request
* @access public
* @return bool
*/
- public function isPost()
+ public function isPost(): bool
{
return $this->method() == 'POST';
}
@@ -857,7 +764,7 @@ class Request
* @access public
* @return bool
*/
- public function isPut()
+ public function isPut(): bool
{
return $this->method() == 'PUT';
}
@@ -867,7 +774,7 @@ class Request
* @access public
* @return bool
*/
- public function isDelete()
+ public function isDelete(): bool
{
return $this->method() == 'DELETE';
}
@@ -877,7 +784,7 @@ class Request
* @access public
* @return bool
*/
- public function isHead()
+ public function isHead(): bool
{
return $this->method() == 'HEAD';
}
@@ -887,7 +794,7 @@ class Request
* @access public
* @return bool
*/
- public function isPatch()
+ public function isPatch(): bool
{
return $this->method() == 'PATCH';
}
@@ -897,7 +804,7 @@ class Request
* @access public
* @return bool
*/
- public function isOptions()
+ public function isOptions(): bool
{
return $this->method() == 'OPTIONS';
}
@@ -907,7 +814,7 @@ class Request
* @access public
* @return bool
*/
- public function isCli()
+ public function isCli(): bool
{
return PHP_SAPI == 'cli';
}
@@ -917,7 +824,7 @@ class Request
* @access public
* @return bool
*/
- public function isCgi()
+ public function isCgi(): bool
{
return strpos(PHP_SAPI, 'cgi') === 0;
}
@@ -925,14 +832,14 @@ class Request
/**
* 获取当前请求的参数
* @access public
- * @param mixed $name 变量名
- * @param mixed $default 默认值
- * @param string|array $filter 过滤方法
+ * @param string|array $name 变量名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
* @return mixed
*/
public function param($name = '', $default = null, $filter = '')
{
- if (!$this->mergeParam) {
+ if (empty($this->mergeParam)) {
$method = $this->method(true);
// 自动获取请求变量
@@ -955,71 +862,124 @@ class Request
$this->mergeParam = true;
}
- if (true === $name) {
- // 获取包含文件上传信息的数组
- $file = $this->file();
- $data = is_array($file) ? array_merge($this->param, $file) : $this->param;
-
- return $this->input($data, '', $default, $filter);
+ if (is_array($name)) {
+ return $this->only($name, $this->param, $filter);
}
return $this->input($this->param, $name, $default, $filter);
}
+ /**
+ * 获取包含文件在内的请求参数
+ * @access public
+ * @param string|array $name 变量名
+ * @param string|array $filter 过滤方法
+ * @return mixed
+ */
+ public function all($name = '', $filter = '')
+ {
+ $data = array_merge($this->param(), $this->file());
+
+ if (is_array($name)) {
+ $data = $this->only($name, $data, $filter);
+ }
+
+ return $data;
+ }
+
/**
* 设置路由变量
* @access public
- * @param array $route 路由变量
+ * @param Rule $rule 路由对象
* @return $this
*/
- public function setRouteVars(array $route)
+ public function setRule(Rule $rule)
{
- $this->route = array_merge($this->route, $route);
+ $this->rule = $rule;
+ return $this;
+ }
+
+ /**
+ * 获取当前路由对象
+ * @access public
+ * @return Rule|null
+ */
+ public function rule()
+ {
+ return $this->rule;
+ }
+
+ /**
+ * 设置路由变量
+ * @access public
+ * @param array $route 路由变量
+ * @return $this
+ */
+ public function setRoute(array $route)
+ {
+ $this->route = array_merge($this->route, $route);
+ $this->mergeParam = false;
return $this;
}
/**
* 获取路由参数
* @access public
- * @param string|false $name 变量名
- * @param mixed $default 默认值
- * @param string|array $filter 过滤方法
+ * @param string|array $name 变量名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
* @return mixed
*/
public function route($name = '', $default = null, $filter = '')
{
+ if (is_array($name)) {
+ return $this->only($name, $this->route, $filter);
+ }
+
return $this->input($this->route, $name, $default, $filter);
}
/**
* 获取GET参数
* @access public
- * @param string|false $name 变量名
- * @param mixed $default 默认值
- * @param string|array $filter 过滤方法
+ * @param string|array $name 变量名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
* @return mixed
*/
public function get($name = '', $default = null, $filter = '')
{
- if (empty($this->get)) {
- $this->get = $_GET;
+ if (is_array($name)) {
+ return $this->only($name, $this->get, $filter);
}
return $this->input($this->get, $name, $default, $filter);
}
+ /**
+ * 获取中间件传递的参数
+ * @access public
+ * @param mixed $name 变量名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function middleware($name, $default = null)
+ {
+ return $this->middleware[$name] ?? $default;
+ }
+
/**
* 获取POST参数
* @access public
- * @param string|false $name 变量名
- * @param mixed $default 默认值
- * @param string|array $filter 过滤方法
+ * @param string|array $name 变量名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
* @return mixed
*/
public function post($name = '', $default = null, $filter = '')
{
- if (empty($this->post)) {
- $this->post = !empty($_POST) ? $_POST : $this->getInputData($this->input);
+ if (is_array($name)) {
+ return $this->only($name, $this->post, $filter);
}
return $this->input($this->post, $name, $default, $filter);
@@ -1028,38 +988,39 @@ class Request
/**
* 获取PUT参数
* @access public
- * @param string|false $name 变量名
- * @param mixed $default 默认值
- * @param string|array $filter 过滤方法
+ * @param string|array $name 变量名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
* @return mixed
*/
public function put($name = '', $default = null, $filter = '')
{
- if (is_null($this->put)) {
- $this->put = $this->getInputData($this->input);
+ if (is_array($name)) {
+ return $this->only($name, $this->put, $filter);
}
return $this->input($this->put, $name, $default, $filter);
}
- protected function getInputData($content)
+ protected function getInputData($content): array
{
- if (false !== strpos($this->contentType(), 'json')) {
- return (array) json_decode($content, true);
- } elseif (strpos($content, '=')) {
+ $contentType = $this->contentType();
+ if ('application/x-www-form-urlencoded' == $contentType) {
parse_str($content, $data);
return $data;
+ } elseif (false !== strpos($contentType, 'json')) {
+ return (array) json_decode($content, true);
}
return [];
}
/**
- * 获取DELETE参数
+ * 设置获取DELETE参数
* @access public
- * @param string|false $name 变量名
- * @param mixed $default 默认值
- * @param string|array $filter 过滤方法
+ * @param mixed $name 变量名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
* @return mixed
*/
public function delete($name = '', $default = null, $filter = '')
@@ -1068,11 +1029,11 @@ class Request
}
/**
- * 获取PATCH参数
+ * 设置获取PATCH参数
* @access public
- * @param string|false $name 变量名
- * @param mixed $default 默认值
- * @param string|array $filter 过滤方法
+ * @param mixed $name 变量名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
* @return mixed
*/
public function patch($name = '', $default = null, $filter = '')
@@ -1083,58 +1044,65 @@ class Request
/**
* 获取request变量
* @access public
- * @param string|false $name 变量名
- * @param mixed $default 默认值
- * @param string|array $filter 过滤方法
+ * @param string|array $name 数据名称
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
* @return mixed
*/
public function request($name = '', $default = null, $filter = '')
{
- if (empty($this->request)) {
- $this->request = $_REQUEST;
+ if (is_array($name)) {
+ return $this->only($name, $this->request, $filter);
}
return $this->input($this->request, $name, $default, $filter);
}
/**
- * 获取session数据
+ * 获取环境变量
* @access public
- * @param string $name 数据名称
- * @param string $default 默认值
+ * @param string $name 数据名称
+ * @param string $default 默认值
* @return mixed
*/
- public function session($name = '', $default = null)
+ public function env(string $name = '', string $default = null)
{
- if (empty($this->session)) {
- $this->session = Session::get();
+ if (empty($name)) {
+ return $this->env->get();
+ } else {
+ $name = strtoupper($name);
}
+ return $this->env->get($name, $default);
+ }
+
+ /**
+ * 获取session数据
+ * @access public
+ * @param string $name 数据名称
+ * @param string $default 默认值
+ * @return mixed
+ */
+ public function session(string $name = '', $default = null)
+ {
if ('' === $name) {
- return $this->session;
+ return $this->session->all();
}
-
- $data = $this->getData($this->session, $name);
-
- return is_null($data) ? $default : $data;
+ return $this->session->get($name, $default);
}
/**
* 获取cookie参数
* @access public
- * @param string $name 变量名
- * @param string $default 默认值
- * @param string|array $filter 过滤方法
+ * @param mixed $name 数据名称
+ * @param string $default 默认值
+ * @param string|array $filter 过滤方法
* @return mixed
*/
- public function cookie($name = '', $default = null, $filter = '')
+ public function cookie(string $name = '', $default = null, $filter = '')
{
- if (empty($this->cookie)) {
- $this->cookie = Cookie::get();
- }
-
if (!empty($name)) {
- $data = Cookie::has($name) ? Cookie::get($name) : $default;
+ $data = $this->getData($this->cookie, $name, $default);
} else {
$data = $this->cookie;
}
@@ -1144,7 +1112,6 @@ class Request
if (is_array($data)) {
array_walk_recursive($data, [$this, 'filterValue'], $filter);
- reset($data);
} else {
$this->filterValue($data, $name, $filter);
}
@@ -1155,11 +1122,11 @@ class Request
/**
* 获取server参数
* @access public
- * @param string $name 数据名称
- * @param string $default 默认值
+ * @param string $name 数据名称
+ * @param string $default 默认值
* @return mixed
*/
- public function server($name = '', $default = null)
+ public function server(string $name = '', string $default = '')
{
if (empty($name)) {
return $this->server;
@@ -1167,25 +1134,22 @@ class Request
$name = strtoupper($name);
}
- return isset($this->server[$name]) ? $this->server[$name] : $default;
+ return $this->server[$name] ?? $default;
}
/**
* 获取上传的文件信息
* @access public
- * @param string $name 名称
- * @return null|array|\think\File
+ * @param string $name 名称
+ * @return null|array|UploadedFile
*/
- public function file($name = '')
+ public function file(string $name = '')
{
- if (empty($this->file)) {
- $this->file = isset($_FILES) ? $_FILES : [];
- }
-
$files = $this->file;
if (!empty($files)) {
+
if (strpos($name, '.')) {
- list($name, $sub) = explode('.', $name);
+ [$name, $sub] = explode('.', $name);
}
// 处理上传文件
@@ -1200,17 +1164,13 @@ class Request
return $array[$name];
}
}
-
- return;
}
- protected function dealUploadFile($files, $name)
+ protected function dealUploadFile(array $files, string $name): array
{
$array = [];
foreach ($files as $key => $file) {
- if ($file instanceof File) {
- $array[$key] = $file;
- } elseif (is_array($file['name'])) {
+ if (is_array($file['name'])) {
$item = [];
$keys = array_keys($file);
$count = count($file['name']);
@@ -1230,20 +1190,24 @@ class Request
$temp[$_key] = $file[$_key][$i];
}
- $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp);
+ $item[] = new UploadedFile($temp['tmp_name'], $temp['name'], $temp['type'], $temp['error']);
}
$array[$key] = $item;
} else {
- if ($file['error'] > 0) {
- if ($key == $name) {
- $this->throwUploadFileError($file['error']);
- } else {
- continue;
+ if ($file instanceof File) {
+ $array[$key] = $file;
+ } else {
+ if ($file['error'] > 0) {
+ if ($key == $name) {
+ $this->throwUploadFileError($file['error']);
+ } else {
+ continue;
+ }
}
- }
- $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file);
+ $array[$key] = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error']);
+ }
}
}
@@ -1262,94 +1226,37 @@ class Request
];
$msg = $fileUploadErrors[$error];
-
- throw new Exception($msg);
+ throw new Exception($msg, $error);
}
/**
- * 获取环境变量
+ * 设置或者获取当前的Header
* @access public
- * @param string $name 数据名称
- * @param string $default 默认值
- * @return mixed
- */
- public function env($name = '', $default = null)
- {
- if (empty($name)) {
- return $this->env;
- } else {
- $name = strtoupper($name);
- }
-
- return isset($this->env[$name]) ? $this->env[$name] : $default;
- }
-
- /**
- * 获取当前的Header
- * @access public
- * @param string $name header名称
- * @param string $default 默认值
+ * @param string $name header名称
+ * @param string $default 默认值
* @return string|array
*/
- public function header($name = '', $default = null)
+ public function header(string $name = '', string $default = null)
{
- if (empty($this->header)) {
- $header = [];
- if (function_exists('apache_request_headers') && $result = apache_request_headers()) {
- $header = $result;
- } else {
- $server = $this->server;
- foreach ($server as $key => $val) {
- if (0 === strpos($key, 'HTTP_')) {
- $key = str_replace('_', '-', strtolower(substr($key, 5)));
- $header[$key] = $val;
- }
- }
- if (isset($server['CONTENT_TYPE'])) {
- $header['content-type'] = $server['CONTENT_TYPE'];
- }
- if (isset($server['CONTENT_LENGTH'])) {
- $header['content-length'] = $server['CONTENT_LENGTH'];
- }
- }
- $this->header = array_change_key_case($header);
- }
-
if ('' === $name) {
return $this->header;
}
$name = str_replace('_', '-', strtolower($name));
- return isset($this->header[$name]) ? $this->header[$name] : $default;
- }
-
- /**
- * 递归重置数组指针
- * @access public
- * @param array $data 数据源
- * @return void
- */
- public function arrayReset(array &$data)
- {
- foreach ($data as &$value) {
- if (is_array($value)) {
- $this->arrayReset($value);
- }
- }
- reset($data);
+ return $this->header[$name] ?? $default;
}
/**
* 获取变量 支持过滤和默认值
* @access public
- * @param array $data 数据源
- * @param string|false $name 字段名
- * @param mixed $default 默认值
- * @param string|array $filter 过滤函数
+ * @param array $data 数据源
+ * @param string|false $name 字段名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤函数
* @return mixed
*/
- public function input($data = [], $name = '', $default = null, $filter = '')
+ public function input(array $data = [], $name = '', $default = null, $filter = '')
{
if (false === $name) {
// 获取原始数据
@@ -1360,7 +1267,7 @@ class Request
if ('' != $name) {
// 解析name
if (strpos($name, '/')) {
- list($name, $type) = explode('/', $name);
+ [$name, $type] = explode('/', $name);
}
$data = $this->getData($data, $name);
@@ -1374,18 +1281,7 @@ class Request
}
}
- // 解析过滤器
- $filter = $this->getFilter($filter, $default);
-
- if (is_array($data)) {
- array_walk_recursive($data, [$this, 'filterValue'], $filter);
- if (version_compare(PHP_VERSION, '7.1.0', '<')) {
- // 恢复PHP版本低于 7.1 时 array_walk_recursive 中消耗的内部指针
- $this->arrayReset($data);
- }
- } else {
- $this->filterValue($data, $name, $filter);
- }
+ $data = $this->filterData($data, $filter, $name, $default);
if (isset($type) && $data !== $default) {
// 强制类型转换
@@ -1395,106 +1291,28 @@ class Request
return $data;
}
- /**
- * 获取数据
- * @access public
- * @param array $data 数据源
- * @param string|false $name 字段名
- * @return mixed
- */
- protected function getData(array $data, $name)
+ protected function filterData($data, $filter, $name, $default)
{
- foreach (explode('.', $name) as $val) {
- if (isset($data[$val])) {
- $data = $data[$val];
- } else {
- return;
- }
+ // 解析过滤器
+ $filter = $this->getFilter($filter, $default);
+
+ if (is_array($data)) {
+ array_walk_recursive($data, [$this, 'filterValue'], $filter);
+ } else {
+ $this->filterValue($data, $name, $filter);
}
return $data;
}
- /**
- * 设置或获取当前的过滤规则
- * @access public
- * @param mixed $filter 过滤规则
- * @return mixed
- */
- public function filter($filter = null)
- {
- if (is_null($filter)) {
- return $this->filter;
- }
-
- $this->filter = $filter;
- }
-
- protected function getFilter($filter, $default)
- {
- if (is_null($filter)) {
- $filter = [];
- } else {
- $filter = $filter ?: $this->filter;
- if (is_string($filter) && false === strpos($filter, '/')) {
- $filter = explode(',', $filter);
- } else {
- $filter = (array) $filter;
- }
- }
-
- $filter[] = $default;
-
- return $filter;
- }
-
- /**
- * 递归过滤给定的值
- * @access public
- * @param mixed $value 键值
- * @param mixed $key 键名
- * @param array $filters 过滤方法+默认值
- * @return mixed
- */
- private function filterValue(&$value, $key, $filters)
- {
- $default = array_pop($filters);
-
- foreach ($filters as $filter) {
- if (is_callable($filter)) {
- // 调用函数或者方法过滤
- $value = call_user_func($filter, $value);
- } elseif (is_scalar($value)) {
- if (false !== strpos($filter, '/')) {
- // 正则过滤
- if (!preg_match($filter, $value)) {
- // 匹配不成功返回默认值
- $value = $default;
- break;
- }
- } elseif (!empty($filter)) {
- // filter函数不存在时, 则使用filter_var进行过滤
- // filter为非整形值时, 调用filter_id取得过滤id
- $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter));
- if (false === $value) {
- $value = $default;
- break;
- }
- }
- }
- }
-
- return $value;
- }
-
/**
* 强制类型转换
* @access public
- * @param string $data
+ * @param mixed $data
* @param string $type
* @return mixed
*/
- private function typeCast(&$data, $type)
+ private function typeCast(&$data, string $type)
{
switch (strtolower($type)) {
// 数组
@@ -1525,23 +1343,118 @@ class Request
}
/**
- * 是否存在某个请求参数
+ * 获取数据
* @access public
- * @param string $name 变量名
- * @param string $type 变量类型
- * @param bool $checkEmpty 是否检测空值
+ * @param array $data 数据源
+ * @param string $name 字段名
+ * @param mixed $default 默认值
* @return mixed
*/
- public function has($name, $type = 'param', $checkEmpty = false)
+ protected function getData(array $data, string $name, $default = null)
{
- if (!in_array($type, ['param', 'get', 'post', 'request', 'put', 'patch', 'file', 'session', 'cookie', 'env', 'header', 'route'])) {
+ foreach (explode('.', $name) as $val) {
+ if (isset($data[$val])) {
+ $data = $data[$val];
+ } else {
+ return $default;
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * 设置或获取当前的过滤规则
+ * @access public
+ * @param mixed $filter 过滤规则
+ * @return mixed
+ */
+ public function filter($filter = null)
+ {
+ if (is_null($filter)) {
+ return $this->filter;
+ }
+
+ $this->filter = $filter;
+
+ return $this;
+ }
+
+ protected function getFilter($filter, $default): array
+ {
+ if (is_null($filter)) {
+ $filter = [];
+ } else {
+ $filter = $filter ?: $this->filter;
+ if (is_string($filter) && false === strpos($filter, '/')) {
+ $filter = explode(',', $filter);
+ } else {
+ $filter = (array) $filter;
+ }
+ }
+
+ $filter[] = $default;
+
+ return $filter;
+ }
+
+ /**
+ * 递归过滤给定的值
+ * @access public
+ * @param mixed $value 键值
+ * @param mixed $key 键名
+ * @param array $filters 过滤方法+默认值
+ * @return mixed
+ */
+ public function filterValue(&$value, $key, $filters)
+ {
+ $default = array_pop($filters);
+
+ foreach ($filters as $filter) {
+ if (is_callable($filter)) {
+ // 调用函数或者方法过滤
+ $value = call_user_func($filter, $value);
+ } elseif (is_scalar($value)) {
+ if (is_string($filter) && false !== strpos($filter, '/')) {
+ // 正则过滤
+ if (!preg_match($filter, $value)) {
+ // 匹配不成功返回默认值
+ $value = $default;
+ break;
+ }
+ } elseif (!empty($filter)) {
+ // filter函数不存在时, 则使用filter_var进行过滤
+ // filter为非整形值时, 调用filter_id取得过滤id
+ $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter));
+ if (false === $value) {
+ $value = $default;
+ break;
+ }
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * 是否存在某个请求参数
+ * @access public
+ * @param string $name 变量名
+ * @param string $type 变量类型
+ * @param bool $checkEmpty 是否检测空值
+ * @return bool
+ */
+ public function has(string $name, string $type = 'param', bool $checkEmpty = false): bool
+ {
+ if (!in_array($type, ['param', 'get', 'post', 'put', 'patch', 'route', 'delete', 'cookie', 'session', 'env', 'request', 'server', 'header', 'file'])) {
return false;
}
- if (empty($this->$type)) {
- $param = $this->$type();
- } else {
- $param = $this->$type;
+ $param = empty($this->$type) ? $this->$type() : $this->$type;
+
+ if (is_object($param)) {
+ return $param->has($name);
}
// 按.拆分成多维数组进行判断
@@ -1559,17 +1472,14 @@ class Request
/**
* 获取指定的参数
* @access public
- * @param string|array $name 变量名
- * @param string $type 变量类型
- * @return mixed
+ * @param array $name 变量名
+ * @param mixed $data 数据或者变量类型
+ * @param string|array $filter 过滤方法
+ * @return array
*/
- public function only($name, $type = 'param')
+ public function only(array $name, $data = 'param', $filter = ''): array
{
- $param = $this->$type();
-
- if (is_string($name)) {
- $name = explode(',', $name);
- }
+ $data = is_array($data) ? $data : $this->$data();
$item = [];
foreach ($name as $key => $val) {
@@ -1577,15 +1487,14 @@ class Request
if (is_int($key)) {
$default = null;
$key = $val;
+ if (!isset($data[$key])) {
+ continue;
+ }
} else {
$default = $val;
}
- if (isset($param[$key])) {
- $item[$key] = $param[$key];
- } elseif (isset($default)) {
- $item[$key] = $default;
- }
+ $item[$key] = $this->filterData($data[$key] ?? $default, $filter, $key, $default);
}
return $item;
@@ -1594,16 +1503,13 @@ class Request
/**
* 排除指定参数获取
* @access public
- * @param string|array $name 变量名
- * @param string $type 变量类型
+ * @param array $name 变量名
+ * @param string $type 变量类型
* @return mixed
*/
- public function except($name, $type = 'param')
+ public function except(array $name, string $type = 'param'): array
{
$param = $this->$type();
- if (is_string($name)) {
- $name = explode(',', $name);
- }
foreach ($name as $key) {
if (isset($param[$key])) {
@@ -1619,7 +1525,7 @@ class Request
* @access public
* @return bool
*/
- public function isSsl()
+ public function isSsl(): bool
{
if ($this->server('HTTPS') && ('1' == $this->server('HTTPS') || 'on' == strtolower($this->server('HTTPS')))) {
return true;
@@ -1629,7 +1535,7 @@ class Request
return true;
} elseif ('https' == $this->server('HTTP_X_FORWARDED_PROTO')) {
return true;
- } elseif ($this->config['https_agent_name'] && $this->server($this->config['https_agent_name'])) {
+ } elseif ($this->httpsAgentName && $this->server($this->httpsAgentName)) {
return true;
}
@@ -1641,101 +1547,167 @@ class Request
* @access public
* @return bool
*/
- public function isJson()
+ public function isJson(): bool
{
- return false !== strpos($this->type(), 'json');
+ $acceptType = $this->type();
+
+ return false !== strpos($acceptType, 'json');
}
/**
* 当前是否Ajax请求
* @access public
- * @param bool $ajax true 获取原始ajax请求
+ * @param bool $ajax true 获取原始ajax请求
* @return bool
*/
- public function isAjax($ajax = false)
+ public function isAjax(bool $ajax = false): bool
{
$value = $this->server('HTTP_X_REQUESTED_WITH');
- $result = 'xmlhttprequest' == strtolower($value) ? true : false;
+ $result = $value && 'xmlhttprequest' == strtolower($value) ? true : false;
if (true === $ajax) {
return $result;
}
- $result = $this->param($this->config['var_ajax']) ? true : $result;
- $this->mergeParam = false;
- return $result;
+ return $this->param($this->varAjax) ? true : $result;
}
/**
* 当前是否Pjax请求
* @access public
- * @param bool $pjax true 获取原始pjax请求
+ * @param bool $pjax true 获取原始pjax请求
* @return bool
*/
- public function isPjax($pjax = false)
+ public function isPjax(bool $pjax = false): bool
{
- $result = !is_null($this->server('HTTP_X_PJAX')) ? true : false;
+ $result = !empty($this->server('HTTP_X_PJAX')) ? true : false;
if (true === $pjax) {
return $result;
}
- $result = $this->param($this->config['var_pjax']) ? true : $result;
- $this->mergeParam = false;
- return $result;
+ return $this->param($this->varPjax) ? true : $result;
}
/**
* 获取客户端IP地址
* @access public
- * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
- * @param boolean $adv 是否进行高级模式获取(有可能被伪装)
- * @return mixed
+ * @return string
*/
- public function ip($type = 0, $adv = true)
+ public function ip(): string
{
- $type = $type ? 1 : 0;
- static $ip = null;
-
- if (null !== $ip) {
- return $ip[$type];
+ if (!empty($this->realIP)) {
+ return $this->realIP;
}
- $httpAgentIp = $this->config['http_agent_ip'];
+ $this->realIP = $this->server('REMOTE_ADDR', '');
- if ($httpAgentIp && $this->server($httpAgentIp)) {
- $ip = $this->server($httpAgentIp);
- } elseif ($adv) {
- if ($this->server('HTTP_X_FORWARDED_FOR')) {
- $arr = explode(',', $this->server('HTTP_X_FORWARDED_FOR'));
- $pos = array_search('unknown', $arr);
- if (false !== $pos) {
- unset($arr[$pos]);
+ // 如果指定了前端代理服务器IP以及其会发送的IP头
+ // 则尝试获取前端代理服务器发送过来的真实IP
+ $proxyIp = $this->proxyServerIp;
+ $proxyIpHeader = $this->proxyServerIpHeader;
+
+ if (count($proxyIp) > 0 && count($proxyIpHeader) > 0) {
+ // 从指定的HTTP头中依次尝试获取IP地址
+ // 直到获取到一个合法的IP地址
+ foreach ($proxyIpHeader as $header) {
+ $tempIP = $this->server($header);
+
+ if (empty($tempIP)) {
+ continue;
+ }
+
+ $tempIP = trim(explode(',', $tempIP)[0]);
+
+ if (!$this->isValidIP($tempIP)) {
+ $tempIP = null;
+ } else {
+ break;
+ }
+ }
+
+ // tempIP不为空,说明获取到了一个IP地址
+ // 这时我们检查 REMOTE_ADDR 是不是指定的前端代理服务器之一
+ // 如果是的话说明该 IP头 是由前端代理服务器设置的
+ // 否则则是伪装的
+ if (!empty($tempIP)) {
+ $realIPBin = $this->ip2bin($this->realIP);
+
+ foreach ($proxyIp as $ip) {
+ $serverIPElements = explode('/', $ip);
+ $serverIP = $serverIPElements[0];
+ $serverIPPrefix = $serverIPElements[1] ?? 128;
+ $serverIPBin = $this->ip2bin($serverIP);
+
+ // IP类型不符
+ if (strlen($realIPBin) !== strlen($serverIPBin)) {
+ continue;
+ }
+
+ if (strncmp($realIPBin, $serverIPBin, (int) $serverIPPrefix) === 0) {
+ $this->realIP = $tempIP;
+ break;
+ }
}
- $ip = trim(current($arr));
- } elseif ($this->server('HTTP_CLIENT_IP')) {
- $ip = $this->server('HTTP_CLIENT_IP');
- } elseif ($this->server('REMOTE_ADDR')) {
- $ip = $this->server('REMOTE_ADDR');
}
- } elseif ($this->server('REMOTE_ADDR')) {
- $ip = $this->server('REMOTE_ADDR');
}
- // IP地址类型
- $ip_mode = (strpos($ip, ':') === false) ? 'ipv4' : 'ipv6';
-
- // IP地址合法验证
- if (filter_var($ip, FILTER_VALIDATE_IP) !== $ip) {
- $ip = ('ipv4' === $ip_mode) ? '0.0.0.0' : '::';
+ if (!$this->isValidIP($this->realIP)) {
+ $this->realIP = '0.0.0.0';
}
- // 如果是ipv4地址,则直接使用ip2long返回int类型ip;如果是ipv6地址,暂时不支持,直接返回0
- $long_ip = ('ipv4' === $ip_mode) ? sprintf("%u", ip2long($ip)) : 0;
+ return $this->realIP;
+ }
- $ip = [$ip, $long_ip];
+ /**
+ * 检测是否是合法的IP地址
+ *
+ * @param string $ip IP地址
+ * @param string $type IP地址类型 (ipv4, ipv6)
+ *
+ * @return boolean
+ */
+ public function isValidIP(string $ip, string $type = ''): bool
+ {
+ switch (strtolower($type)) {
+ case 'ipv4':
+ $flag = FILTER_FLAG_IPV4;
+ break;
+ case 'ipv6':
+ $flag = FILTER_FLAG_IPV6;
+ break;
+ default:
+ $flag = 0;
+ break;
+ }
- return $ip[$type];
+ return boolval(filter_var($ip, FILTER_VALIDATE_IP, $flag));
+ }
+
+ /**
+ * 将IP地址转换为二进制字符串
+ *
+ * @param string $ip
+ *
+ * @return string
+ */
+ public function ip2bin(string $ip): string
+ {
+ if ($this->isValidIP($ip, 'ipv6')) {
+ $IPHex = str_split(bin2hex(inet_pton($ip)), 4);
+ foreach ($IPHex as $key => $value) {
+ $IPHex[$key] = intval($value, 16);
+ }
+ $IPBin = vsprintf('%016b%016b%016b%016b%016b%016b%016b%016b', $IPHex);
+ } else {
+ $IPHex = str_split(bin2hex(inet_pton($ip)), 2);
+ foreach ($IPHex as $key => $value) {
+ $IPHex[$key] = intval($value, 16);
+ }
+ $IPBin = vsprintf('%08b%08b%08b%08b', $IPHex);
+ }
+
+ return $IPBin;
}
/**
@@ -1743,7 +1715,7 @@ class Request
* @access public
* @return bool
*/
- public function isMobile()
+ public function isMobile(): bool
{
if ($this->server('HTTP_VIA') && stristr($this->server('HTTP_VIA'), "wap")) {
return true;
@@ -1763,7 +1735,7 @@ class Request
* @access public
* @return string
*/
- public function scheme()
+ public function scheme(): string
{
return $this->isSsl() ? 'https' : 'http';
}
@@ -1773,9 +1745,9 @@ class Request
* @access public
* @return string
*/
- public function query()
+ public function query(): string
{
- return $this->server('QUERY_STRING');
+ return $this->server('QUERY_STRING', '');
}
/**
@@ -1784,7 +1756,7 @@ class Request
* @param string $host 主机名(含端口)
* @return $this
*/
- public function setHost($host)
+ public function setHost(string $host)
{
$this->host = $host;
@@ -1797,23 +1769,25 @@ class Request
* @param bool $strict true 仅仅获取HOST
* @return string
*/
- public function host($strict = false)
+ public function host(bool $strict = false): string
{
- if (!$this->host) {
- $this->host = $this->server('HTTP_X_REAL_HOST') ?: $this->server('HTTP_HOST');
+ if ($this->host) {
+ $host = $this->host;
+ } else {
+ $host = strval($this->server('HTTP_X_FORWARDED_HOST') ?: $this->server('HTTP_HOST'));
}
- return true === $strict && strpos($this->host, ':') ? strstr($this->host, ':', true) : $this->host;
+ return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host;
}
/**
* 当前请求URL地址中的port参数
* @access public
- * @return integer
+ * @return int
*/
- public function port()
+ public function port(): int
{
- return $this->server('SERVER_PORT');
+ return (int) ($this->server('HTTP_X_FORWARDED_PORT') ?: $this->server('SERVER_PORT', ''));
}
/**
@@ -1821,19 +1795,19 @@ class Request
* @access public
* @return string
*/
- public function protocol()
+ public function protocol(): string
{
- return $this->server('SERVER_PROTOCOL');
+ return $this->server('SERVER_PROTOCOL', '');
}
/**
* 当前请求 REMOTE_PORT
* @access public
- * @return integer
+ * @return int
*/
- public function remotePort()
+ public function remotePort(): int
{
- return $this->server('REMOTE_PORT');
+ return (int) $this->server('REMOTE_PORT', '');
}
/**
@@ -1841,13 +1815,13 @@ class Request
* @access public
* @return string
*/
- public function contentType()
+ public function contentType(): string
{
- $contentType = $this->server('CONTENT_TYPE');
+ $contentType = $this->header('Content-Type');
if ($contentType) {
if (strpos($contentType, ';')) {
- list($type) = explode(';', $contentType);
+ [$type] = explode(';', $contentType);
} else {
$type = $contentType;
}
@@ -1857,42 +1831,12 @@ class Request
return '';
}
- /**
- * 获取当前请求的路由信息
- * @access public
- * @param array $route 路由名称
- * @return array
- */
- public function routeInfo(array $route = [])
- {
- if (!empty($route)) {
- $this->routeInfo = $route;
- }
-
- return $this->routeInfo;
- }
-
- /**
- * 设置或者获取当前请求的调度信息
- * @access public
- * @param \think\route\Dispatch $dispatch 调度信息
- * @return \think\route\Dispatch
- */
- public function dispatch($dispatch = null)
- {
- if (!is_null($dispatch)) {
- $this->dispatch = $dispatch;
- }
-
- return $this->dispatch;
- }
-
/**
* 获取当前请求的安全Key
* @access public
* @return string
*/
- public function secureKey()
+ public function secureKey(): string
{
if (is_null($this->secureKey)) {
$this->secureKey = uniqid('', true);
@@ -1901,25 +1845,13 @@ class Request
return $this->secureKey;
}
- /**
- * 设置当前的模块名
- * @access public
- * @param string $module 模块名
- * @return $this
- */
- public function setModule($module)
- {
- $this->module = $module;
- return $this;
- }
-
/**
* 设置当前的控制器名
* @access public
* @param string $controller 控制器名
* @return $this
*/
- public function setController($controller)
+ public function setController(string $controller)
{
$this->controller = $controller;
return $this;
@@ -1931,29 +1863,19 @@ class Request
* @param string $action 操作名
* @return $this
*/
- public function setAction($action)
+ public function setAction(string $action)
{
$this->action = $action;
return $this;
}
- /**
- * 获取当前的模块名
- * @access public
- * @return string
- */
- public function module()
- {
- return $this->module ?: '';
- }
-
/**
* 获取当前的控制器名
* @access public
* @param bool $convert 转换为小写
* @return string
*/
- public function controller($convert = false)
+ public function controller(bool $convert = false): string
{
$name = $this->controller ?: '';
return $convert ? strtolower($name) : $name;
@@ -1962,35 +1884,13 @@ class Request
/**
* 获取当前的操作名
* @access public
- * @param bool $convert 转换为驼峰
+ * @param bool $convert 转换为小写
* @return string
*/
- public function action($convert = false)
+ public function action(bool $convert = false): string
{
$name = $this->action ?: '';
- return $convert ? $name : strtolower($name);
- }
-
- /**
- * 设置当前的语言
- * @access public
- * @param string $lang 语言名
- * @return $this
- */
- public function setLangset($lang)
- {
- $this->langset = $lang;
- return $this;
- }
-
- /**
- * 获取当前的语言
- * @access public
- * @return string
- */
- public function langset()
- {
- return $this->langset ?: '';
+ return $convert ? strtolower($name) : $name;
}
/**
@@ -1998,7 +1898,7 @@ class Request
* @access public
* @return string
*/
- public function getContent()
+ public function getContent(): string
{
if (is_null($this->content)) {
$this->content = $this->input;
@@ -2012,7 +1912,7 @@ class Request
* @access public
* @return string
*/
- public function getInput()
+ public function getInput(): string
{
return $this->input;
}
@@ -2024,96 +1924,67 @@ class Request
* @param mixed $type 令牌生成方法
* @return string
*/
- public function token($name = '__token__', $type = null)
+ public function buildToken(string $name = '__token__', $type = 'md5'): string
{
$type = is_callable($type) ? $type : 'md5';
$token = call_user_func($type, $this->server('REQUEST_TIME_FLOAT'));
- if ($this->isAjax()) {
- header($name . ': ' . $token);
- }
-
- facade\Session::set($name, $token);
+ $this->session->set($name, $token);
return $token;
}
/**
- * 设置当前地址的请求缓存
+ * 检查请求令牌
* @access public
- * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id
- * @param mixed $expire 缓存有效期
- * @param array $except 缓存排除
- * @param string $tag 缓存标签
- * @return mixed
+ * @param string $token 令牌名称
+ * @param array $data 表单数据
+ * @return bool
*/
- public function cache($key, $expire = null, $except = [], $tag = null)
+ public function checkToken(string $token = '__token__', array $data = []): bool
{
- if (!is_array($except)) {
- $tag = $except;
- $except = [];
+ if (in_array($this->method(), ['GET', 'HEAD', 'OPTIONS'], true)) {
+ return true;
}
- if (false === $key || !$this->isGet() || $this->isCheckCache || false === $expire) {
- // 关闭当前缓存
- return;
+ if (!$this->session->has($token)) {
+ // 令牌数据无效
+ return false;
}
- // 标记请求缓存检查
- $this->isCheckCache = true;
-
- foreach ($except as $rule) {
- if (0 === stripos($this->url(), $rule)) {
- return;
- }
+ // Header验证
+ if ($this->header('X-CSRF-TOKEN') && $this->session->get($token) === $this->header('X-CSRF-TOKEN')) {
+ // 防止重复提交
+ $this->session->delete($token); // 验证完成销毁session
+ return true;
}
- if ($key instanceof \Closure) {
- $key = call_user_func_array($key, [$this]);
- } elseif (true === $key) {
- // 自动缓存功能
- $key = '__URL__';
- } elseif (strpos($key, '|')) {
- list($key, $fun) = explode('|', $key);
+ if (empty($data)) {
+ $data = $this->post();
}
- // 特殊规则替换
- if (false !== strpos($key, '__')) {
- $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__'], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key);
+ // 令牌验证
+ if (isset($data[$token]) && $this->session->get($token) === $data[$token]) {
+ // 防止重复提交
+ $this->session->delete($token); // 验证完成销毁session
+ return true;
}
- if (false !== strpos($key, ':')) {
- $param = $this->param();
- foreach ($param as $item => $val) {
- if (is_string($val) && false !== strpos($key, ':' . $item)) {
- $key = str_replace(':' . $item, $val, $key);
- }
- }
- } elseif (strpos($key, ']')) {
- if ('[' . $this->ext() . ']' == $key) {
- // 缓存某个后缀的请求
- $key = md5($this->url());
- } else {
- return;
- }
- }
-
- if (isset($fun)) {
- $key = $fun($key);
- }
-
- $this->cache = [$key, $expire, $tag];
- return $this->cache;
+ // 开启TOKEN重置
+ $this->session->delete($token);
+ return false;
}
/**
- * 读取请求缓存设置
+ * 设置在中间件传递的数据
* @access public
- * @return array
+ * @param array $middleware 数据
+ * @return $this
*/
- public function getCache()
+ public function withMiddleware(array $middleware)
{
- return $this->cache;
+ $this->middleware = array_merge($this->middleware, $middleware);
+ return $this;
}
/**
@@ -2140,34 +2011,10 @@ class Request
return $this;
}
- /**
- * 设置php://input数据
- * @access public
- * @param string $input RAW数据
- * @return $this
- */
- public function withInput($input)
- {
- $this->input = $input;
- return $this;
- }
-
- /**
- * 设置文件上传数据
- * @access public
- * @param array $files 上传信息
- * @return $this
- */
- public function withFiles(array $files)
- {
- $this->file = $files;
- return $this;
- }
-
/**
* 设置COOKIE数据
* @access public
- * @param array $cookie 数据
+ * @param array $cookie 数据
* @return $this
*/
public function withCookie(array $cookie)
@@ -2176,6 +2023,18 @@ class Request
return $this;
}
+ /**
+ * 设置SESSION数据
+ * @access public
+ * @param Session $session 数据
+ * @return $this
+ */
+ public function withSession(Session $session)
+ {
+ $this->session = $session;
+ return $this;
+ }
+
/**
* 设置SERVER数据
* @access public
@@ -2203,15 +2062,46 @@ class Request
/**
* 设置ENV数据
* @access public
- * @param array $env 数据
+ * @param Env $env 数据
* @return $this
*/
- public function withEnv(array $env)
+ public function withEnv(Env $env)
{
$this->env = $env;
return $this;
}
+ /**
+ * 设置php://input数据
+ * @access public
+ * @param string $input RAW数据
+ * @return $this
+ */
+ public function withInput(string $input)
+ {
+ $this->input = $input;
+ if (!empty($input)) {
+ $inputData = $this->getInputData($input);
+ if (!empty($inputData)) {
+ $this->post = $inputData;
+ $this->put = $inputData;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * 设置文件上传数据
+ * @access public
+ * @param array $files 上传信息
+ * @return $this
+ */
+ public function withFiles(array $files)
+ {
+ $this->file = $files;
+ return $this;
+ }
+
/**
* 设置ROUTE变量
* @access public
@@ -2225,43 +2115,53 @@ class Request
}
/**
- * 设置请求数据
+ * 设置中间传递数据
* @access public
* @param string $name 参数名
* @param mixed $value 值
*/
- public function __set($name, $value)
+ public function __set(string $name, $value)
{
- return $this->param[$name] = $value;
+ $this->middleware[$name] = $value;
}
/**
- * 获取请求数据的值
+ * 获取中间传递数据的值
* @access public
- * @param string $name 参数名
+ * @param string $name 名称
* @return mixed
*/
- public function __get($name)
+ public function __get(string $name)
{
- return $this->param($name);
+ return $this->middleware($name);
}
/**
- * 检测请求数据的值
+ * 检测中间传递数据的值
* @access public
* @param string $name 名称
* @return boolean
*/
- public function __isset($name)
+ public function __isset(string $name): bool
{
- return isset($this->param[$name]);
+ return isset($this->middleware[$name]);
}
- public function __debugInfo()
+ // ArrayAccess
+ public function offsetExists($name): bool
{
- $data = get_object_vars($this);
- unset($data['dispatch'], $data['config']);
-
- return $data;
+ return $this->has($name);
}
+
+ public function offsetGet($name)
+ {
+ return $this->param($name);
+ }
+
+ public function offsetSet($name, $value)
+ {}
+
+ public function offsetUnset($name)
+ {}
+
}
diff --git a/thinkphp/library/think/Response.php b/vendor/topthink/framework/src/think/Response.php
old mode 100755
new mode 100644
similarity index 64%
rename from thinkphp/library/think/Response.php
rename to vendor/topthink/framework/src/think/Response.php
index 5fa5402ab..a8a61ffb2
--- a/thinkphp/library/think/Response.php
+++ b/vendor/topthink/framework/src/think/Response.php
@@ -2,18 +2,21 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think;
-use think\response\Redirect as RedirectResponse;
-
-class Response
+/**
+ * 响应输出基础类
+ * @package think
+ */
+abstract class Response
{
/**
* 原始数据
@@ -21,12 +24,6 @@ class Response
*/
protected $data;
- /**
- * 应用对象实例
- * @var App
- */
- protected $app;
-
/**
* 当前contentType
* @var string
@@ -70,47 +67,56 @@ class Response
protected $content = null;
/**
- * 架构函数
- * @access public
- * @param mixed $data 输出数据
- * @param int $code
- * @param array $header
- * @param array $options 输出参数
+ * Cookie对象
+ * @var Cookie
*/
- public function __construct($data = '', $code = 200, array $header = [], $options = [])
+ protected $cookie;
+
+ /**
+ * Session对象
+ * @var Session
+ */
+ protected $session;
+
+ /**
+ * 初始化
+ * @access protected
+ * @param mixed $data 输出数据
+ * @param int $code 状态码
+ */
+ protected function init($data = '', int $code = 200)
{
$this->data($data);
-
- if (!empty($options)) {
- $this->options = array_merge($this->options, $options);
- }
+ $this->code = $code;
$this->contentType($this->contentType, $this->charset);
-
- $this->code = $code;
- $this->app = Container::get('app');
- $this->header = array_merge($this->header, $header);
}
/**
* 创建Response对象
* @access public
- * @param mixed $data 输出数据
- * @param string $type 输出类型
- * @param int $code
- * @param array $header
- * @param array $options 输出参数
+ * @param mixed $data 输出数据
+ * @param string $type 输出类型
+ * @param int $code 状态码
* @return Response
*/
- public static function create($data = '', $type = '', $code = 200, array $header = [], $options = [])
+ public static function create($data = '', string $type = 'html', int $code = 200): Response
{
$class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type));
- if (class_exists($class)) {
- return new $class($data, $code, $header, $options);
- }
+ return Container::getInstance()->invokeClass($class, [$data, $code]);
+ }
- return new static($data, $code, $header, $options);
+ /**
+ * 设置Session对象
+ * @access public
+ * @param Session $session Session对象
+ * @return $this
+ */
+ public function setSession(Session $session)
+ {
+ $this->session = $session;
+ return $this;
}
/**
@@ -119,30 +125,11 @@ class Response
* @return void
* @throws \InvalidArgumentException
*/
- public function send()
+ public function send(): void
{
- // 监听response_send
- $this->app['hook']->listen('response_send', $this);
-
// 处理输出数据
$data = $this->getContent();
- // Trace调试注入
- if ('cli' != PHP_SAPI && $this->app['env']->get('app_trace', $this->app->config('app.app_trace'))) {
- $this->app['debug']->inject($this, $data);
- }
-
- if (200 == $this->code && $this->allowCache) {
- $cache = $this->app['request']->getCache();
- if ($cache) {
- $this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate';
- $this->header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT';
- $this->header['Expires'] = gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT';
-
- $this->app['cache']->tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]);
- }
- }
-
if (!headers_sent() && !empty($this->header)) {
// 发送状态码
http_response_code($this->code);
@@ -151,6 +138,9 @@ class Response
header($name . (!is_null($val) ? ':' . $val : ''));
}
}
+ if ($this->cookie) {
+ $this->cookie->save();
+ }
$this->sendData($data);
@@ -158,14 +148,6 @@ class Response
// 提高页面响应
fastcgi_finish_request();
}
-
- // 监听response_end
- $this->app['hook']->listen('response_end', $this);
-
- // 清空当次请求有效的数据
- if (!($this instanceof RedirectResponse)) {
- $this->app['session']->flush();
- }
}
/**
@@ -185,7 +167,7 @@ class Response
* @param string $data 要处理的数据
* @return void
*/
- protected function sendData($data)
+ protected function sendData(string $data): void
{
echo $data;
}
@@ -196,7 +178,7 @@ class Response
* @param mixed $options 输出参数
* @return $this
*/
- public function options($options = [])
+ public function options(array $options = [])
{
$this->options = array_merge($this->options, $options);
@@ -222,7 +204,7 @@ class Response
* @param bool $cache 允许请求缓存
* @return $this
*/
- public function allowCache($cache)
+ public function allowCache(bool $cache)
{
$this->allowCache = $cache;
@@ -230,19 +212,39 @@ class Response
}
/**
- * 设置响应头
+ * 是否允许请求缓存
* @access public
- * @param string|array $name 参数名
- * @param string $value 参数值
+ * @return bool
+ */
+ public function isAllowCache()
+ {
+ return $this->allowCache;
+ }
+
+ /**
+ * 设置Cookie
+ * @access public
+ * @param string $name cookie名称
+ * @param string $value cookie值
+ * @param mixed $option 可选参数
* @return $this
*/
- public function header($name, $value = null)
+ public function cookie(string $name, string $value, $option = null)
{
- if (is_array($name)) {
- $this->header = array_merge($this->header, $name);
- } else {
- $this->header[$name] = $value;
- }
+ $this->cookie->set($name, $value, $option);
+
+ return $this;
+ }
+
+ /**
+ * 设置响应头
+ * @access public
+ * @param array $header 参数
+ * @return $this
+ */
+ public function header(array $header = [])
+ {
+ $this->header = array_merge($this->header, $header);
return $this;
}
@@ -274,7 +276,7 @@ class Response
* @param integer $code 状态码
* @return $this
*/
- public function code($code)
+ public function code(int $code)
{
$this->code = $code;
@@ -287,7 +289,7 @@ class Response
* @param string $time
* @return $this
*/
- public function lastModified($time)
+ public function lastModified(string $time)
{
$this->header['Last-Modified'] = $time;
@@ -300,7 +302,7 @@ class Response
* @param string $time
* @return $this
*/
- public function expires($time)
+ public function expires(string $time)
{
$this->header['Expires'] = $time;
@@ -313,7 +315,7 @@ class Response
* @param string $eTag
* @return $this
*/
- public function eTag($eTag)
+ public function eTag(string $eTag)
{
$this->header['ETag'] = $eTag;
@@ -323,29 +325,16 @@ class Response
/**
* 页面缓存控制
* @access public
- * @param string $cache 缓存设置
+ * @param string $cache 状态码
* @return $this
*/
- public function cacheControl($cache)
+ public function cacheControl(string $cache)
{
$this->header['Cache-control'] = $cache;
return $this;
}
- /**
- * 设置页面不做任何缓存
- * @access public
- * @return $this
- */
- public function noCache()
- {
- $this->header['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0';
- $this->header['Pragma'] = 'no-cache';
-
- return $this;
- }
-
/**
* 页面输出类型
* @access public
@@ -353,7 +342,7 @@ class Response
* @param string $charset 输出编码
* @return $this
*/
- public function contentType($contentType, $charset = 'utf-8')
+ public function contentType(string $contentType, string $charset = 'utf-8')
{
$this->header['Content-Type'] = $contentType . '; charset=' . $charset;
@@ -366,10 +355,10 @@ class Response
* @param string $name 头部名称
* @return mixed
*/
- public function getHeader($name = '')
+ public function getHeader(string $name = '')
{
if (!empty($name)) {
- return isset($this->header[$name]) ? $this->header[$name] : null;
+ return $this->header[$name] ?? null;
}
return $this->header;
@@ -388,9 +377,9 @@ class Response
/**
* 获取输出数据
* @access public
- * @return mixed
+ * @return string
*/
- public function getContent()
+ public function getContent(): string
{
if (null == $this->content) {
$content = $this->output($this->data);
@@ -414,16 +403,8 @@ class Response
* @access public
* @return integer
*/
- public function getCode()
+ public function getCode(): int
{
return $this->code;
}
-
- public function __debugInfo()
- {
- $data = get_object_vars($this);
- unset($data['app']);
-
- return $data;
- }
}
diff --git a/vendor/topthink/framework/src/think/Route.php b/vendor/topthink/framework/src/think/Route.php
new file mode 100644
index 000000000..a3acf85b5
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Route.php
@@ -0,0 +1,926 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Closure;
+use think\exception\RouteNotFoundException;
+use think\route\Dispatch;
+use think\route\dispatch\Callback;
+use think\route\dispatch\Url as UrlDispatch;
+use think\route\Domain;
+use think\route\Resource;
+use think\route\Rule;
+use think\route\RuleGroup;
+use think\route\RuleItem;
+use think\route\RuleName;
+use think\route\Url as UrlBuild;
+
+/**
+ * 路由管理类
+ * @package think
+ */
+class Route
+{
+ /**
+ * REST定义
+ * @var array
+ */
+ protected $rest = [
+ 'index' => ['get', '', 'index'],
+ 'create' => ['get', '/create', 'create'],
+ 'edit' => ['get', '//edit', 'edit'],
+ 'read' => ['get', '/', 'read'],
+ 'save' => ['post', '', 'save'],
+ 'update' => ['put', '/', 'update'],
+ 'delete' => ['delete', '/', 'delete'],
+ ];
+
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [
+ // pathinfo分隔符
+ 'pathinfo_depr' => '/',
+ // 是否开启路由延迟解析
+ 'url_lazy_route' => false,
+ // 是否强制使用路由
+ 'url_route_must' => false,
+ // 合并路由规则
+ 'route_rule_merge' => false,
+ // 路由是否完全匹配
+ 'route_complete_match' => false,
+ // 去除斜杠
+ 'remove_slash' => false,
+ // 使用注解路由
+ 'route_annotation' => false,
+ // 默认的路由变量规则
+ 'default_route_pattern' => '[\w\.]+',
+ // URL伪静态后缀
+ 'url_html_suffix' => 'html',
+ // 访问控制器层名称
+ 'controller_layer' => 'controller',
+ // 空控制器名
+ 'empty_controller' => 'Error',
+ // 是否使用控制器后缀
+ 'controller_suffix' => false,
+ // 默认控制器名
+ 'default_controller' => 'Index',
+ // 默认操作名
+ 'default_action' => 'index',
+ // 操作方法后缀
+ 'action_suffix' => '',
+ // 非路由变量是否使用普通参数方式(用于URL生成)
+ 'url_common_param' => true,
+ ];
+
+ /**
+ * 当前应用
+ * @var App
+ */
+ protected $app;
+
+ /**
+ * 请求对象
+ * @var Request
+ */
+ protected $request;
+
+ /**
+ * @var RuleName
+ */
+ protected $ruleName;
+
+ /**
+ * 当前HOST
+ * @var string
+ */
+ protected $host;
+
+ /**
+ * 当前分组对象
+ * @var RuleGroup
+ */
+ protected $group;
+
+ /**
+ * 路由绑定
+ * @var array
+ */
+ protected $bind = [];
+
+ /**
+ * 域名对象
+ * @var Domain[]
+ */
+ protected $domains = [];
+
+ /**
+ * 跨域路由规则
+ * @var RuleGroup
+ */
+ protected $cross;
+
+ /**
+ * 路由是否延迟解析
+ * @var bool
+ */
+ protected $lazy = false;
+
+ /**
+ * 路由是否测试模式
+ * @var bool
+ */
+ protected $isTest = false;
+
+ /**
+ * (分组)路由规则是否合并解析
+ * @var bool
+ */
+ protected $mergeRuleRegex = false;
+
+ /**
+ * 是否去除URL最后的斜线
+ * @var bool
+ */
+ protected $removeSlash = false;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ $this->ruleName = new RuleName();
+ $this->setDefaultDomain();
+
+ if (is_file($this->app->getRuntimePath() . 'route.php')) {
+ // 读取路由映射文件
+ $this->import(include $this->app->getRuntimePath() . 'route.php');
+ }
+
+ $this->config = array_merge($this->config, $this->app->config->get('route'));
+ }
+
+ protected function init()
+ {
+ if (!empty($this->config['middleware'])) {
+ $this->app->middleware->import($this->config['middleware'], 'route');
+ }
+
+ $this->lazy($this->config['url_lazy_route']);
+ $this->mergeRuleRegex = $this->config['route_rule_merge'];
+ $this->removeSlash = $this->config['remove_slash'];
+
+ $this->group->removeSlash($this->removeSlash);
+ }
+
+ public function config(string $name = null)
+ {
+ if (is_null($name)) {
+ return $this->config;
+ }
+
+ return $this->config[$name] ?? null;
+ }
+
+ /**
+ * 设置路由域名及分组(包括资源路由)是否延迟解析
+ * @access public
+ * @param bool $lazy 路由是否延迟解析
+ * @return $this
+ */
+ public function lazy(bool $lazy = true)
+ {
+ $this->lazy = $lazy;
+ return $this;
+ }
+
+ /**
+ * 设置路由为测试模式
+ * @access public
+ * @param bool $test 路由是否测试模式
+ * @return void
+ */
+ public function setTestMode(bool $test): void
+ {
+ $this->isTest = $test;
+ }
+
+ /**
+ * 检查路由是否为测试模式
+ * @access public
+ * @return bool
+ */
+ public function isTest(): bool
+ {
+ return $this->isTest;
+ }
+
+ /**
+ * 设置路由域名及分组(包括资源路由)是否合并解析
+ * @access public
+ * @param bool $merge 路由是否合并解析
+ * @return $this
+ */
+ public function mergeRuleRegex(bool $merge = true)
+ {
+ $this->mergeRuleRegex = $merge;
+ $this->group->mergeRuleRegex($merge);
+
+ return $this;
+ }
+
+ /**
+ * 初始化默认域名
+ * @access protected
+ * @return void
+ */
+ protected function setDefaultDomain(): void
+ {
+ // 注册默认域名
+ $domain = new Domain($this);
+
+ $this->domains['-'] = $domain;
+
+ // 默认分组
+ $this->group = $domain;
+ }
+
+ /**
+ * 设置当前分组
+ * @access public
+ * @param RuleGroup $group 域名
+ * @return void
+ */
+ public function setGroup(RuleGroup $group): void
+ {
+ $this->group = $group;
+ }
+
+ /**
+ * 获取指定标识的路由分组 不指定则获取当前分组
+ * @access public
+ * @param string $name 分组标识
+ * @return RuleGroup
+ */
+ public function getGroup(string $name = null)
+ {
+ return $name ? $this->ruleName->getGroup($name) : $this->group;
+ }
+
+ /**
+ * 注册变量规则
+ * @access public
+ * @param array $pattern 变量规则
+ * @return $this
+ */
+ public function pattern(array $pattern)
+ {
+ $this->group->pattern($pattern);
+
+ return $this;
+ }
+
+ /**
+ * 注册路由参数
+ * @access public
+ * @param array $option 参数
+ * @return $this
+ */
+ public function option(array $option)
+ {
+ $this->group->option($option);
+
+ return $this;
+ }
+
+ /**
+ * 注册域名路由
+ * @access public
+ * @param string|array $name 子域名
+ * @param mixed $rule 路由规则
+ * @return Domain
+ */
+ public function domain($name, $rule = null): Domain
+ {
+ // 支持多个域名使用相同路由规则
+ $domainName = is_array($name) ? array_shift($name) : $name;
+
+ if (!isset($this->domains[$domainName])) {
+ $domain = (new Domain($this, $domainName, $rule))
+ ->lazy($this->lazy)
+ ->removeSlash($this->removeSlash)
+ ->mergeRuleRegex($this->mergeRuleRegex);
+
+ $this->domains[$domainName] = $domain;
+ } else {
+ $domain = $this->domains[$domainName];
+ $domain->parseGroupRule($rule);
+ }
+
+ if (is_array($name) && !empty($name)) {
+ foreach ($name as $item) {
+ $this->domains[$item] = $domainName;
+ }
+ }
+
+ // 返回域名对象
+ return $domain;
+ }
+
+ /**
+ * 获取域名
+ * @access public
+ * @return array
+ */
+ public function getDomains(): array
+ {
+ return $this->domains;
+ }
+
+ /**
+ * 获取RuleName对象
+ * @access public
+ * @return RuleName
+ */
+ public function getRuleName(): RuleName
+ {
+ return $this->ruleName;
+ }
+
+ /**
+ * 设置路由绑定
+ * @access public
+ * @param string $bind 绑定信息
+ * @param string $domain 域名
+ * @return $this
+ */
+ public function bind(string $bind, string $domain = null)
+ {
+ $domain = is_null($domain) ? '-' : $domain;
+
+ $this->bind[$domain] = $bind;
+
+ return $this;
+ }
+
+ /**
+ * 读取路由绑定信息
+ * @access public
+ * @return array
+ */
+ public function getBind(): array
+ {
+ return $this->bind;
+ }
+
+ /**
+ * 读取路由绑定
+ * @access public
+ * @param string $domain 域名
+ * @return string|null
+ */
+ public function getDomainBind(string $domain = null)
+ {
+ if (is_null($domain)) {
+ $domain = $this->host;
+ } elseif (false === strpos($domain, '.') && $this->request) {
+ $domain .= '.' . $this->request->rootDomain();
+ }
+
+ if ($this->request) {
+ $subDomain = $this->request->subDomain();
+
+ if (strpos($subDomain, '.')) {
+ $name = '*' . strstr($subDomain, '.');
+ }
+ }
+
+ if (isset($this->bind[$domain])) {
+ $result = $this->bind[$domain];
+ } elseif (isset($name) && isset($this->bind[$name])) {
+ $result = $this->bind[$name];
+ } elseif (!empty($subDomain) && isset($this->bind['*'])) {
+ $result = $this->bind['*'];
+ } else {
+ $result = null;
+ }
+
+ return $result;
+ }
+
+ /**
+ * 读取路由标识
+ * @access public
+ * @param string $name 路由标识
+ * @param string $domain 域名
+ * @param string $method 请求类型
+ * @return array
+ */
+ public function getName(string $name = null, string $domain = null, string $method = '*'): array
+ {
+ return $this->ruleName->getName($name, $domain, $method);
+ }
+
+ /**
+ * 批量导入路由标识
+ * @access public
+ * @param array $name 路由标识
+ * @return void
+ */
+ public function import(array $name): void
+ {
+ $this->ruleName->import($name);
+ }
+
+ /**
+ * 注册路由标识
+ * @access public
+ * @param string $name 路由标识
+ * @param RuleItem $ruleItem 路由规则
+ * @param bool $first 是否优先
+ * @return void
+ */
+ public function setName(string $name, RuleItem $ruleItem, bool $first = false): void
+ {
+ $this->ruleName->setName($name, $ruleItem, $first);
+ }
+
+ /**
+ * 保存路由规则
+ * @access public
+ * @param string $rule 路由规则
+ * @param RuleItem $ruleItem RuleItem对象
+ * @return void
+ */
+ public function setRule(string $rule, RuleItem $ruleItem = null): void
+ {
+ $this->ruleName->setRule($rule, $ruleItem);
+ }
+
+ /**
+ * 读取路由
+ * @access public
+ * @param string $rule 路由规则
+ * @return RuleItem[]
+ */
+ public function getRule(string $rule): array
+ {
+ return $this->ruleName->getRule($rule);
+ }
+
+ /**
+ * 读取路由列表
+ * @access public
+ * @return array
+ */
+ public function getRuleList(): array
+ {
+ return $this->ruleName->getRuleList();
+ }
+
+ /**
+ * 清空路由规则
+ * @access public
+ * @return void
+ */
+ public function clear(): void
+ {
+ $this->ruleName->clear();
+
+ if ($this->group) {
+ $this->group->clear();
+ }
+ }
+
+ /**
+ * 注册路由规则
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @param string $method 请求类型
+ * @return RuleItem
+ */
+ public function rule(string $rule, $route = null, string $method = '*'): RuleItem
+ {
+ if ($route instanceof Response) {
+ // 兼容之前的路由到响应对象,感觉不需要,使用场景很少,闭包就能实现
+ $route = function () use ($route) {
+ return $route;
+ };
+ }
+ return $this->group->addRule($rule, $route, $method);
+ }
+
+ /**
+ * 设置跨域有效路由规则
+ * @access public
+ * @param Rule $rule 路由规则
+ * @param string $method 请求类型
+ * @return $this
+ */
+ public function setCrossDomainRule(Rule $rule, string $method = '*')
+ {
+ if (!isset($this->cross)) {
+ $this->cross = (new RuleGroup($this))->mergeRuleRegex($this->mergeRuleRegex);
+ }
+
+ $this->cross->addRuleItem($rule, $method);
+
+ return $this;
+ }
+
+ /**
+ * 注册路由分组
+ * @access public
+ * @param string|\Closure $name 分组名称或者参数
+ * @param mixed $route 分组路由
+ * @return RuleGroup
+ */
+ public function group($name, $route = null): RuleGroup
+ {
+ if ($name instanceof Closure) {
+ $route = $name;
+ $name = '';
+ }
+
+ return (new RuleGroup($this, $this->group, $name, $route))
+ ->lazy($this->lazy)
+ ->removeSlash($this->removeSlash)
+ ->mergeRuleRegex($this->mergeRuleRegex);
+ }
+
+ /**
+ * 注册路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @return RuleItem
+ */
+ public function any(string $rule, $route): RuleItem
+ {
+ return $this->rule($rule, $route, '*');
+ }
+
+ /**
+ * 注册GET路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @return RuleItem
+ */
+ public function get(string $rule, $route): RuleItem
+ {
+ return $this->rule($rule, $route, 'GET');
+ }
+
+ /**
+ * 注册POST路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @return RuleItem
+ */
+ public function post(string $rule, $route): RuleItem
+ {
+ return $this->rule($rule, $route, 'POST');
+ }
+
+ /**
+ * 注册PUT路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @return RuleItem
+ */
+ public function put(string $rule, $route): RuleItem
+ {
+ return $this->rule($rule, $route, 'PUT');
+ }
+
+ /**
+ * 注册DELETE路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @return RuleItem
+ */
+ public function delete(string $rule, $route): RuleItem
+ {
+ return $this->rule($rule, $route, 'DELETE');
+ }
+
+ /**
+ * 注册PATCH路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @return RuleItem
+ */
+ public function patch(string $rule, $route): RuleItem
+ {
+ return $this->rule($rule, $route, 'PATCH');
+ }
+
+ /**
+ * 注册OPTIONS路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @return RuleItem
+ */
+ public function options(string $rule, $route): RuleItem
+ {
+ return $this->rule($rule, $route, 'OPTIONS');
+ }
+
+ /**
+ * 注册资源路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param string $route 路由地址
+ * @return Resource
+ */
+ public function resource(string $rule, string $route): Resource
+ {
+ return (new Resource($this, $this->group, $rule, $route, $this->rest))
+ ->lazy($this->lazy);
+ }
+
+ /**
+ * 注册视图路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param string $template 路由模板地址
+ * @param array $vars 模板变量
+ * @return RuleItem
+ */
+ public function view(string $rule, string $template = '', array $vars = []): RuleItem
+ {
+ return $this->rule($rule, function () use ($vars, $template) {
+ return Response::create($template, 'view')->assign($vars);
+ }, 'GET');
+ }
+
+ /**
+ * 注册重定向路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param string $route 路由地址
+ * @param int $status 状态码
+ * @return RuleItem
+ */
+ public function redirect(string $rule, string $route = '', int $status = 301): RuleItem
+ {
+ return $this->rule($rule, function (Request $request) use ($status, $route) {
+ $search = $replace = [];
+ $matches = $request->rule()->getVars();
+
+ foreach ($matches as $key => $value) {
+ $search[] = '<' . $key . '>';
+ $replace[] = $value;
+
+ $search[] = ':' . $key;
+ $replace[] = $value;
+ }
+
+ $route = str_replace($search, $replace, $route);
+ return Response::create($route, 'redirect')->code($status);
+ }, '*');
+ }
+
+ /**
+ * rest方法定义和修改
+ * @access public
+ * @param string|array $name 方法名称
+ * @param array|bool $resource 资源
+ * @return $this
+ */
+ public function rest($name, $resource = [])
+ {
+ if (is_array($name)) {
+ $this->rest = $resource ? $name : array_merge($this->rest, $name);
+ } else {
+ $this->rest[$name] = $resource;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 获取rest方法定义的参数
+ * @access public
+ * @param string $name 方法名称
+ * @return array|null
+ */
+ public function getRest(string $name = null)
+ {
+ if (is_null($name)) {
+ return $this->rest;
+ }
+
+ return $this->rest[$name] ?? null;
+ }
+
+ /**
+ * 注册未匹配路由规则后的处理
+ * @access public
+ * @param string|Closure $route 路由地址
+ * @param string $method 请求类型
+ * @return RuleItem
+ */
+ public function miss($route, string $method = '*'): RuleItem
+ {
+ return $this->group->miss($route, $method);
+ }
+
+ /**
+ * 路由调度
+ * @param Request $request
+ * @param Closure|bool $withRoute
+ * @return Response
+ */
+ public function dispatch(Request $request, $withRoute = true)
+ {
+ $this->request = $request;
+ $this->host = $this->request->host(true);
+ $this->init();
+
+ if ($withRoute) {
+ //加载路由
+ if ($withRoute instanceof Closure) {
+ $withRoute();
+ }
+ $dispatch = $this->check();
+ } else {
+ $dispatch = $this->url($this->path());
+ }
+
+ $dispatch->init($this->app);
+
+ return $this->app->middleware->pipeline('route')
+ ->send($request)
+ ->then(function () use ($dispatch) {
+ return $dispatch->run();
+ });
+ }
+
+ /**
+ * 检测URL路由
+ * @access public
+ * @return Dispatch|false
+ * @throws RouteNotFoundException
+ */
+ public function check()
+ {
+ // 自动检测域名路由
+ $url = str_replace($this->config['pathinfo_depr'], '|', $this->path());
+
+ $completeMatch = $this->config['route_complete_match'];
+
+ $result = $this->checkDomain()->check($this->request, $url, $completeMatch);
+
+ if (false === $result && !empty($this->cross)) {
+ // 检测跨域路由
+ $result = $this->cross->check($this->request, $url, $completeMatch);
+ }
+
+ if (false !== $result) {
+ return $result;
+ } elseif ($this->config['url_route_must']) {
+ throw new RouteNotFoundException();
+ }
+
+ return $this->url($url);
+ }
+
+ /**
+ * 获取当前请求URL的pathinfo信息(不含URL后缀)
+ * @access protected
+ * @return string
+ */
+ protected function path(): string
+ {
+ $suffix = $this->config['url_html_suffix'];
+ $pathinfo = $this->request->pathinfo();
+
+ if (false === $suffix) {
+ // 禁止伪静态访问
+ $path = $pathinfo;
+ } elseif ($suffix) {
+ // 去除正常的URL后缀
+ $path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);
+ } else {
+ // 允许任何后缀访问
+ $path = preg_replace('/\.' . $this->request->ext() . '$/i', '', $pathinfo);
+ }
+
+ return $path;
+ }
+
+ /**
+ * 默认URL解析
+ * @access public
+ * @param string $url URL地址
+ * @return Dispatch
+ */
+ public function url(string $url): Dispatch
+ {
+ if ($this->request->method() == 'OPTIONS') {
+ // 自动响应options请求
+ return new Callback($this->request, $this->group, function () {
+ return Response::create('', 'html', 204)->header(['Allow' => 'GET, POST, PUT, DELETE']);
+ });
+ }
+
+ return new UrlDispatch($this->request, $this->group, $url);
+ }
+
+ /**
+ * 检测域名的路由规则
+ * @access protected
+ * @return Domain
+ */
+ protected function checkDomain(): Domain
+ {
+ $item = false;
+
+ if (count($this->domains) > 1) {
+ // 获取当前子域名
+ $subDomain = $this->request->subDomain();
+
+ $domain = $subDomain ? explode('.', $subDomain) : [];
+ $domain2 = $domain ? array_pop($domain) : '';
+
+ if ($domain) {
+ // 存在三级域名
+ $domain3 = array_pop($domain);
+ }
+
+ if (isset($this->domains[$this->host])) {
+ // 子域名配置
+ $item = $this->domains[$this->host];
+ } elseif (isset($this->domains[$subDomain])) {
+ $item = $this->domains[$subDomain];
+ } elseif (isset($this->domains['*.' . $domain2]) && !empty($domain3)) {
+ // 泛三级域名
+ $item = $this->domains['*.' . $domain2];
+ $panDomain = $domain3;
+ } elseif (isset($this->domains['*']) && !empty($domain2)) {
+ // 泛二级域名
+ if ('www' != $domain2) {
+ $item = $this->domains['*'];
+ $panDomain = $domain2;
+ }
+ }
+
+ if (isset($panDomain)) {
+ // 保存当前泛域名
+ $this->request->setPanDomain($panDomain);
+ }
+ }
+
+ if (false === $item) {
+ // 检测全局域名规则
+ $item = $this->domains['-'];
+ }
+
+ if (is_string($item)) {
+ $item = $this->domains[$item];
+ }
+
+ return $item;
+ }
+
+ /**
+ * URL生成 支持路由反射
+ * @access public
+ * @param string $url 路由地址
+ * @param array $vars 参数 ['a'=>'val1', 'b'=>'val2']
+ * @return UrlBuild
+ */
+ public function buildUrl(string $url = '', array $vars = []): UrlBuild
+ {
+ return $this->app->make(UrlBuild::class, [$this, $this->app, $url, $vars], true);
+ }
+
+ /**
+ * 设置全局的路由分组参数
+ * @access public
+ * @param string $method 方法名
+ * @param array $args 调用参数
+ * @return RuleGroup
+ */
+ public function __call($method, $args)
+ {
+ return call_user_func_array([$this->group, $method], $args);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Service.php b/vendor/topthink/framework/src/think/Service.php
new file mode 100644
index 000000000..d9e89601a
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Service.php
@@ -0,0 +1,66 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Closure;
+use think\event\RouteLoaded;
+
+/**
+ * 系统服务基础类
+ * @method void register()
+ * @method void boot()
+ */
+abstract class Service
+{
+ protected $app;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * 加载路由
+ * @access protected
+ * @param string $path 路由路径
+ */
+ protected function loadRoutesFrom($path)
+ {
+ $this->registerRoutes(function () use ($path) {
+ include $path;
+ });
+ }
+
+ /**
+ * 注册路由
+ * @param Closure $closure
+ */
+ protected function registerRoutes(Closure $closure)
+ {
+ $this->app->event->listen(RouteLoaded::class, $closure);
+ }
+
+ /**
+ * 添加指令
+ * @access protected
+ * @param array|string $commands 指令
+ */
+ protected function commands($commands)
+ {
+ $commands = is_array($commands) ? $commands : func_get_args();
+
+ Console::starting(function (Console $console) use ($commands) {
+ $console->addCommands($commands);
+ });
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Session.php b/vendor/topthink/framework/src/think/Session.php
new file mode 100644
index 000000000..6c84faf8f
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Session.php
@@ -0,0 +1,65 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use think\helper\Arr;
+use think\session\Store;
+
+/**
+ * Session管理类
+ * @package think
+ * @mixin Store
+ */
+class Session extends Manager
+{
+ protected $namespace = '\\think\\session\\driver\\';
+
+ protected function createDriver(string $name)
+ {
+ $handler = parent::createDriver($name);
+
+ return new Store($this->getConfig('name') ?: 'PHPSESSID', $handler, $this->getConfig('serialize'));
+ }
+
+ /**
+ * 获取Session配置
+ * @access public
+ * @param null|string $name 名称
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function getConfig(string $name = null, $default = null)
+ {
+ if (!is_null($name)) {
+ return $this->app->config->get('session.' . $name, $default);
+ }
+
+ return $this->app->config->get('session');
+ }
+
+ protected function resolveConfig(string $name)
+ {
+ $config = $this->app->config->get('session', []);
+ Arr::forget($config, 'type');
+ return $config;
+ }
+
+ /**
+ * 默认驱动
+ * @return string|null
+ */
+ public function getDefaultDriver()
+ {
+ return $this->app->config->get('session.type', 'file');
+ }
+}
diff --git a/thinkphp/library/think/Validate.php b/vendor/topthink/framework/src/think/Validate.php
old mode 100755
new mode 100644
similarity index 60%
rename from thinkphp/library/think/Validate.php
rename to vendor/topthink/framework/src/think/Validate.php
index 54c3a7370..078840641
--- a/thinkphp/library/think/Validate.php
+++ b/vendor/topthink/framework/src/think/Validate.php
@@ -2,26 +2,32 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think;
-use think\exception\ClassNotFoundException;
+use Closure;
+use think\exception\ValidateException;
+use think\helper\Str;
use think\validate\ValidateRule;
+/**
+ * 数据验证类
+ * @package think
+ */
class Validate
{
-
/**
* 自定义验证类型
* @var array
*/
- protected static $type = [];
+ protected $type = [];
/**
* 验证类型别名
@@ -53,7 +59,7 @@ class Validate
* 默认规则提示
* @var array
*/
- protected static $typeMsg = [
+ protected $typeMsg = [
'require' => ':attribute require',
'must' => ':attribute must',
'number' => ':attribute must be numeric',
@@ -87,8 +93,6 @@ class Validate
'min' => 'min size of :attribute must be :rule',
'after' => ':attribute cannot be less than :rule',
'before' => ':attribute cannot exceed :rule',
- 'afterWith' => ':attribute cannot be less than :rule',
- 'beforeWith' => ':attribute cannot exceed :rule',
'expire' => ':attribute not within :rule',
'allowIp' => 'access IP is not allowed',
'denyIp' => 'access IP denied',
@@ -110,9 +114,26 @@ class Validate
/**
* 当前验证场景
+ * @var string
+ */
+ protected $currentScene;
+
+ /**
+ * 内置正则验证规则
* @var array
*/
- protected $currentScene = null;
+ protected $defaultRegex = [
+ 'alpha' => '/^[A-Za-z]+$/',
+ 'alphaNum' => '/^[A-Za-z0-9]+$/',
+ 'alphaDash' => '/^[A-Za-z0-9\-\_]+$/',
+ 'chs' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}]+$/u',
+ 'chsAlpha' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}a-zA-Z]+$/u',
+ 'chsAlphaNum' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}a-zA-Z0-9]+$/u',
+ 'chsDash' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}a-zA-Z0-9\_\-]+$/u',
+ 'mobile' => '/^1[3-9]\d{9}$/',
+ 'idCard' => '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)/',
+ 'zip' => '/\d{6}/',
+ ];
/**
* Filter_var 规则
@@ -127,21 +148,6 @@ class Validate
'float' => FILTER_VALIDATE_FLOAT,
];
- /**
- * 内置正则验证规则
- * @var array
- */
- protected $regex = [
- 'alphaDash' => '/^[A-Za-z0-9\-\_]+$/',
- 'chs' => '/^[\x{4e00}-\x{9fa5}]+$/u',
- 'chsAlpha' => '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u',
- 'chsAlphaNum' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u',
- 'chsDash' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u',
- 'mobile' => '/^1[3-9][0-9]\d{8}$/',
- 'idCard' => '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/',
- 'zip' => '/\d{6}/',
- ];
-
/**
* 验证场景定义
* @var array
@@ -150,7 +156,7 @@ class Validate
/**
* 验证失败错误信息
- * @var array
+ * @var string|array
*/
protected $error = [];
@@ -160,6 +166,12 @@ class Validate
*/
protected $batch = false;
+ /**
+ * 验证失败是否抛出异常
+ * @var bool
+ */
+ protected $failException = false;
+
/**
* 场景需要验证的规则
* @var array
@@ -179,37 +191,96 @@ class Validate
protected $append = [];
/**
- * 架构函数
- * @access public
- * @param array $rules 验证规则
- * @param array $message 验证提示信息
- * @param array $field 验证字段描述信息
+ * 验证正则定义
+ * @var array
*/
- public function __construct(array $rules = [], array $message = [], array $field = [])
+ protected $regex = [];
+
+ /**
+ * Db对象
+ * @var Db
+ */
+ protected $db;
+
+ /**
+ * 语言对象
+ * @var Lang
+ */
+ protected $lang;
+
+ /**
+ * 请求对象
+ * @var Request
+ */
+ protected $request;
+
+ /**
+ * @var Closure[]
+ */
+ protected static $maker = [];
+
+ /**
+ * 构造方法
+ * @access public
+ */
+ public function __construct()
{
- $this->rule = $rules + $this->rule;
- $this->message = array_merge($this->message, $message);
- $this->field = array_merge($this->field, $field);
+ if (!empty(static::$maker)) {
+ foreach (static::$maker as $maker) {
+ call_user_func($maker, $this);
+ }
+ }
}
/**
- * 创建一个验证器类
+ * 设置服务注入
* @access public
- * @param array $rules 验证规则
- * @param array $message 验证提示信息
- * @param array $field 验证字段描述信息
- * @return Validate
+ * @param Closure $maker
+ * @return void
*/
- public static function make(array $rules = [], array $message = [], array $field = [])
+ public static function maker(Closure $maker)
{
- return new self($rules, $message, $field);
+ static::$maker[] = $maker;
+ }
+
+ /**
+ * 设置Lang对象
+ * @access public
+ * @param Lang $lang Lang对象
+ * @return void
+ */
+ public function setLang(Lang $lang)
+ {
+ $this->lang = $lang;
+ }
+
+ /**
+ * 设置Db对象
+ * @access public
+ * @param Db $db Db对象
+ * @return void
+ */
+ public function setDb(Db $db)
+ {
+ $this->db = $db;
+ }
+
+ /**
+ * 设置Request对象
+ * @access public
+ * @param Request $request Request对象
+ * @return void
+ */
+ public function setRequest(Request $request)
+ {
+ $this->request = $request;
}
/**
* 添加字段验证规则
* @access protected
- * @param string|array $name 字段名称或者规则数组
- * @param mixed $rule 验证规则或者字段描述信息
+ * @param string|array $name 字段名称或者规则数组
+ * @param mixed $rule 验证规则或者字段描述信息
* @return $this
*/
public function rule($name, $rule = '')
@@ -227,51 +298,49 @@ class Validate
}
/**
- * 注册扩展验证(类型)规则
+ * 注册验证(类型)规则
* @access public
- * @param string $type 验证规则类型
- * @param mixed $callback callback方法(或闭包)
- * @return void
+ * @param string $type 验证规则类型
+ * @param callable $callback callback方法(或闭包)
+ * @param string $message 验证失败提示信息
+ * @return $this
*/
- public static function extend($type, $callback = null)
+ public function extend(string $type, callable $callback = null, string $message = null)
{
- if (is_array($type)) {
- self::$type = array_merge(self::$type, $type);
- } else {
- self::$type[$type] = $callback;
+ $this->type[$type] = $callback;
+
+ if ($message) {
+ $this->typeMsg[$type] = $message;
}
+
+ return $this;
}
/**
* 设置验证规则的默认提示信息
* @access public
- * @param string|array $type 验证规则类型名称或者数组
- * @param string $msg 验证提示信息
+ * @param string|array $type 验证规则类型名称或者数组
+ * @param string $msg 验证提示信息
* @return void
*/
- public static function setTypeMsg($type, $msg = null)
+ public function setTypeMsg($type, string $msg = null): void
{
if (is_array($type)) {
- self::$typeMsg = array_merge(self::$typeMsg, $type);
+ $this->typeMsg = array_merge($this->typeMsg, $type);
} else {
- self::$typeMsg[$type] = $msg;
+ $this->typeMsg[$type] = $msg;
}
}
/**
* 设置提示信息
* @access public
- * @param string|array $name 字段名称
- * @param string $message 提示信息
+ * @param array $message 错误信息
* @return Validate
*/
- public function message($name, $message = '')
+ public function message(array $message)
{
- if (is_array($name)) {
- $this->message = array_merge($this->message, $name);
- } else {
- $this->message[$name] = $message;
- }
+ $this->message = array_merge($this->message, $message);
return $this;
}
@@ -279,10 +348,10 @@ class Validate
/**
* 设置验证场景
* @access public
- * @param string $name 场景名
+ * @param string $name 场景名
* @return $this
*/
- public function scene($name)
+ public function scene(string $name)
{
// 设置当前场景
$this->currentScene = $name;
@@ -293,10 +362,10 @@ class Validate
/**
* 判断是否存在某个验证场景
* @access public
- * @param string $name 场景名
+ * @param string $name 场景名
* @return bool
*/
- public function hasScene($name)
+ public function hasScene(string $name): bool
{
return isset($this->scene[$name]) || method_exists($this, 'scene' . $name);
}
@@ -304,10 +373,10 @@ class Validate
/**
* 设置批量验证
* @access public
- * @param bool $batch 是否批量验证
+ * @param bool $batch 是否批量验证
* @return $this
*/
- public function batch($batch = true)
+ public function batch(bool $batch = true)
{
$this->batch = $batch;
@@ -315,12 +384,25 @@ class Validate
}
/**
- * 指定需要验证的字段列表
- * @access public
- * @param array $fields 字段名
+ * 设置验证失败后是否抛出异常
+ * @access protected
+ * @param bool $fail 是否抛出异常
* @return $this
*/
- public function only($fields)
+ public function failException(bool $fail = true)
+ {
+ $this->failException = $fail;
+
+ return $this;
+ }
+
+ /**
+ * 指定需要验证的字段列表
+ * @access public
+ * @param array $fields 字段名
+ * @return $this
+ */
+ public function only(array $fields)
{
$this->only = $fields;
@@ -330,8 +412,8 @@ class Validate
/**
* 移除某个字段的验证规则
* @access public
- * @param string|array $field 字段名
- * @param mixed $rule 验证规则 null 移除所有规则
+ * @param string|array $field 字段名
+ * @param mixed $rule 验证规则 true 移除所有规则
* @return $this
*/
public function remove($field, $rule = null)
@@ -358,8 +440,8 @@ class Validate
/**
* 追加某个字段的验证规则
* @access public
- * @param string|array $field 字段名
- * @param mixed $rule 验证规则
+ * @param string|array $field 字段名
+ * @param mixed $rule 验证规则
* @return $this
*/
public function append($field, $rule = null)
@@ -382,26 +464,27 @@ class Validate
/**
* 数据自动验证
* @access public
- * @param array $data 数据
- * @param mixed $rules 验证规则
- * @param string $scene 验证场景
+ * @param array $data 数据
+ * @param array $rules 验证规则
* @return bool
*/
- public function check($data, $rules = [], $scene = '')
+ public function check(array $data, array $rules = []): bool
{
$this->error = [];
+ if ($this->currentScene) {
+ $this->getScene($this->currentScene);
+ }
+
if (empty($rules)) {
// 读取验证规则
$rules = $this->rule;
}
- // 获取场景定义
- $this->getScene($scene);
-
foreach ($this->append as $key => $rule) {
if (!isset($rules[$key])) {
$rules[$key] = $rule;
+ unset($this->append[$key]);
}
}
@@ -409,9 +492,9 @@ class Validate
// field => 'rule1|rule2...' field => ['rule1','rule2',...]
if (strpos($key, '|')) {
// 字段|描述 用于指定属性名称
- list($key, $title) = explode('|', $key);
+ [$key, $title] = explode('|', $key);
} else {
- $title = isset($this->field[$key]) ? $this->field[$key] : $key;
+ $title = $this->field[$key] ?? $key;
}
// 场景检测
@@ -419,12 +502,12 @@ class Validate
continue;
}
- // 获取数据 支持多维数组
+ // 获取数据 支持二维数组
$value = $this->getDataValue($data, $key);
// 字段验证
- if ($rule instanceof \Closure) {
- $result = call_user_func_array($rule, [$value, $data, $title, $this]);
+ if ($rule instanceof Closure) {
+ $result = call_user_func_array($rule, [$value, $data]);
} elseif ($rule instanceof ValidateRule) {
// 验证因子
$result = $this->checkItem($key, $value, $rule->getRule(), $data, $rule->getTitle() ?: $title, $rule->getMsg());
@@ -436,11 +519,9 @@ class Validate
// 没有返回true 则表示验证失败
if (!empty($this->batch)) {
// 批量验证
- if (is_array($result)) {
- $this->error = array_merge($this->error, $result);
- } else {
- $this->error[$key] = $result;
- }
+ $this->error[$key] = $result;
+ } elseif ($this->failException) {
+ throw new ValidateException($result);
} else {
$this->error = $result;
return false;
@@ -448,19 +529,26 @@ class Validate
}
}
- return !empty($this->error) ? false : true;
+ if (!empty($this->error)) {
+ if ($this->failException) {
+ throw new ValidateException($this->error);
+ }
+ return false;
+ }
+
+ return true;
}
/**
* 根据验证规则验证数据
* @access public
- * @param mixed $value 字段值
- * @param mixed $rules 验证规则
+ * @param mixed $value 字段值
+ * @param mixed $rules 验证规则
* @return bool
*/
- public function checkRule($value, $rules)
+ public function checkRule($value, $rules): bool
{
- if ($rules instanceof \Closure) {
+ if ($rules instanceof Closure) {
return call_user_func_array($rules, [$value]);
} elseif ($rules instanceof ValidateRule) {
$rules = $rules->getRule();
@@ -469,18 +557,22 @@ class Validate
}
foreach ($rules as $key => $rule) {
- if ($rule instanceof \Closure) {
+ if ($rule instanceof Closure) {
$result = call_user_func_array($rule, [$value]);
} else {
// 判断验证类型
- list($type, $rule) = $this->getValidateType($key, $rule);
+ [$type, $rule] = $this->getValidateType($key, $rule);
- $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type];
+ $callback = $this->type[$type] ?? [$this, $type];
$result = call_user_func_array($callback, [$value, $rule]);
}
if (true !== $result) {
+ if ($this->failException) {
+ throw new ValidateException($result);
+ }
+
return $result;
}
}
@@ -491,15 +583,15 @@ class Validate
/**
* 验证单个字段规则
* @access protected
- * @param string $field 字段名
- * @param mixed $value 字段值
- * @param mixed $rules 验证规则
- * @param array $data 数据
- * @param string $title 字段描述
- * @param array $msg 提示信息
+ * @param string $field 字段名
+ * @param mixed $value 字段值
+ * @param mixed $rules 验证规则
+ * @param array $data 数据
+ * @param string $title 字段描述
+ * @param array $msg 提示信息
* @return mixed
*/
- protected function checkItem($field, $value, $rules, $data, $title = '', $msg = [])
+ protected function checkItem(string $field, $value, $rules, $data, string $title = '', array $msg = [])
{
if (isset($this->remove[$field]) && true === $this->remove[$field] && empty($this->append[$field])) {
// 字段已经移除 无需验证
@@ -513,33 +605,33 @@ class Validate
if (isset($this->append[$field])) {
// 追加额外的验证规则
- $rules = array_merge($rules, $this->append[$field]);
+ $rules = array_unique(array_merge($rules, $this->append[$field]), SORT_REGULAR);
+ unset($this->append[$field]);
}
- $i = 0;
- $result = true;
+ if (empty($rules)) {
+ return true;
+ }
+ $i = 0;
foreach ($rules as $key => $rule) {
- if ($rule instanceof \Closure) {
+ if ($rule instanceof Closure) {
$result = call_user_func_array($rule, [$value, $data]);
$info = is_numeric($key) ? '' : $key;
} else {
// 判断验证类型
- list($type, $rule, $info) = $this->getValidateType($key, $rule);
+ [$type, $rule, $info] = $this->getValidateType($key, $rule);
if (isset($this->append[$field]) && in_array($info, $this->append[$field])) {
-
- } elseif (array_key_exists($field, $this->remove) && (null === $this->remove[$field] || in_array($info, $this->remove[$field]))) {
+ } elseif (isset($this->remove[$field]) && in_array($info, $this->remove[$field])) {
// 规则已经移除
$i++;
continue;
}
- // 验证类型
- if (isset(self::$type[$type])) {
- $result = call_user_func_array(self::$type[$type], [$value, $rule, $data, $field, $title]);
+ if (isset($this->type[$type])) {
+ $result = call_user_func_array($this->type[$type], [$value, $rule, $data, $field, $title]);
} elseif ('must' == $info || 0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) {
- // 验证数据
$result = call_user_func_array([$this, $type], [$value, $rule, $data, $field, $title]);
} else {
$result = true;
@@ -551,7 +643,7 @@ class Validate
if (!empty($msg[$i])) {
$message = $msg[$i];
if (is_string($message) && strpos($message, '{%') === 0) {
- $message = facade\Lang::get(substr($message, 2, -1));
+ $message = $this->lang->get(substr($message, 2, -1));
}
} else {
$message = $this->getRuleMsg($field, $title, $info, $rule);
@@ -561,10 +653,11 @@ class Validate
} elseif (true !== $result) {
// 返回自定义错误信息
if (is_string($result) && false !== strpos($result, ':')) {
- $result = str_replace(
- [':attribute', ':rule'],
- [$title, (string) $rule],
- $result);
+ $result = str_replace(':attribute', $title, $result);
+
+ if (strpos($result, ':rule') && is_scalar($rule)) {
+ $result = str_replace(':rule', (string) $rule, $result);
+ }
}
return $result;
@@ -572,25 +665,29 @@ class Validate
$i++;
}
- return $result;
+ return $result ?? true;
}
/**
* 获取当前验证类型及规则
* @access public
- * @param mixed $key
- * @param mixed $rule
+ * @param mixed $key
+ * @param mixed $rule
* @return array
*/
- protected function getValidateType($key, $rule)
+ protected function getValidateType($key, $rule): array
{
// 判断验证类型
if (!is_numeric($key)) {
+ if (isset($this->alias[$key])) {
+ // 判断别名
+ $key = $this->alias[$key];
+ }
return [$key, $rule, $key];
}
if (strpos($rule, ':')) {
- list($type, $rule) = explode(':', $rule, 2);
+ [$type, $rule] = explode(':', $rule, 2);
if (isset($this->alias[$type])) {
// 判断别名
$type = $this->alias[$type];
@@ -611,13 +708,13 @@ class Validate
/**
* 验证是否和某个字段的值一致
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @param array $data 数据
- * @param string $field 字段名
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @param string $field 字段名
* @return bool
*/
- public function confirm($value, $rule, $data = [], $field = '')
+ public function confirm($value, $rule, array $data = [], string $field = ''): bool
{
if ('' == $rule) {
if (strpos($field, '_confirm')) {
@@ -633,12 +730,12 @@ class Validate
/**
* 验证是否和某个字段的值是否不同
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @param array $data 数据
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
* @return bool
*/
- public function different($value, $rule, $data = [])
+ public function different($value, $rule, array $data = []): bool
{
return $this->getDataValue($data, $rule) != $value;
}
@@ -646,12 +743,12 @@ class Validate
/**
* 验证是否大于等于某个值
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @param array $data 数据
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
* @return bool
*/
- public function egt($value, $rule, $data = [])
+ public function egt($value, $rule, array $data = []): bool
{
return $value >= $this->getDataValue($data, $rule);
}
@@ -659,12 +756,12 @@ class Validate
/**
* 验证是否大于某个值
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @param array $data 数据
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
* @return bool
*/
- public function gt($value, $rule, $data)
+ public function gt($value, $rule, array $data = []): bool
{
return $value > $this->getDataValue($data, $rule);
}
@@ -672,12 +769,12 @@ class Validate
/**
* 验证是否小于等于某个值
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @param array $data 数据
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
* @return bool
*/
- public function elt($value, $rule, $data = [])
+ public function elt($value, $rule, array $data = []): bool
{
return $value <= $this->getDataValue($data, $rule);
}
@@ -685,12 +782,12 @@ class Validate
/**
* 验证是否小于某个值
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @param array $data 数据
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
* @return bool
*/
- public function lt($value, $rule, $data = [])
+ public function lt($value, $rule, array $data = []): bool
{
return $value < $this->getDataValue($data, $rule);
}
@@ -698,11 +795,11 @@ class Validate
/**
* 验证是否等于某个值
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
* @return bool
*/
- public function eq($value, $rule)
+ public function eq($value, $rule): bool
{
return $value == $rule;
}
@@ -710,11 +807,11 @@ class Validate
/**
* 必须验证
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
* @return bool
*/
- public function must($value, $rule = null)
+ public function must($value, $rule = null): bool
{
return !empty($value) || '0' == $value;
}
@@ -722,14 +819,14 @@ class Validate
/**
* 验证字段值是否为有效格式
* @access public
- * @param mixed $value 字段值
- * @param string $rule 验证规则
- * @param array $data 验证数据
+ * @param mixed $value 字段值
+ * @param string $rule 验证规则
+ * @param array $data 数据
* @return bool
*/
- public function is($value, $rule, $data = [])
+ public function is($value, string $rule, array $data = []): bool
{
- switch (Loader::parseName($rule, 1, false)) {
+ switch (Str::camel($rule)) {
case 'require':
// 必须
$result = !empty($value) || '0' == $value;
@@ -771,9 +868,9 @@ class Validate
$result = $this->token($value, '__token__', $data);
break;
default:
- if (isset(self::$type[$rule])) {
+ if (isset($this->type[$rule])) {
// 注册的验证规则
- $result = call_user_func_array(self::$type[$rule], [$value]);
+ $result = call_user_func_array($this->type[$rule], [$value]);
} elseif (function_exists('ctype_' . $rule)) {
// ctype验证规则
$ctypeFun = 'ctype_' . $rule;
@@ -806,13 +903,27 @@ class Validate
}
/**
- * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型
+ * 验证表单令牌
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
* @return bool
*/
- public function activeUrl($value, $rule = 'MX')
+ public function token($value, string $rule, array $data): bool
+ {
+ $rule = !empty($rule) ? $rule : '__token__';
+ return $this->request->checkToken($rule, $data);
+ }
+
+ /**
+ * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function activeUrl(string $value, string $rule = 'MX'): bool
{
if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) {
$rule = 'MX';
@@ -824,11 +935,11 @@ class Validate
/**
* 验证是否有效IP
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则 ipv4 ipv6
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则 ipv4 ipv6
* @return bool
*/
- public function ip($value, $rule = 'ipv4')
+ public function ip($value, string $rule = 'ipv4'): bool
{
if (!in_array($rule, ['ipv4', 'ipv6'])) {
$rule = 'ipv4';
@@ -838,23 +949,67 @@ class Validate
}
/**
- * 验证上传文件后缀
+ * 检测上传文件后缀
* @access public
- * @param mixed $file 上传文件
- * @param mixed $rule 验证规则
+ * @param File $file
+ * @param array|string $ext 允许后缀
* @return bool
*/
- public function fileExt($file, $rule)
+ protected function checkExt(File $file, $ext): bool
+ {
+ if (is_string($ext)) {
+ $ext = explode(',', $ext);
+ }
+
+ return in_array(strtolower($file->extension()), $ext);
+ }
+
+ /**
+ * 检测上传文件大小
+ * @access public
+ * @param File $file
+ * @param integer $size 最大大小
+ * @return bool
+ */
+ protected function checkSize(File $file, $size): bool
+ {
+ return $file->getSize() <= (int) $size;
+ }
+
+ /**
+ * 检测上传文件类型
+ * @access public
+ * @param File $file
+ * @param array|string $mime 允许类型
+ * @return bool
+ */
+ protected function checkMime(File $file, $mime): bool
+ {
+ if (is_string($mime)) {
+ $mime = explode(',', $mime);
+ }
+
+ return in_array(strtolower($file->getMime()), $mime);
+ }
+
+ /**
+ * 验证上传文件后缀
+ * @access public
+ * @param mixed $file 上传文件
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function fileExt($file, $rule): bool
{
if (is_array($file)) {
foreach ($file as $item) {
- if (!($item instanceof File) || !$item->checkExt($rule)) {
+ if (!($item instanceof File) || !$this->checkExt($item, $rule)) {
return false;
}
}
return true;
} elseif ($file instanceof File) {
- return $file->checkExt($rule);
+ return $this->checkExt($file, $rule);
}
return false;
@@ -863,21 +1018,21 @@ class Validate
/**
* 验证上传文件类型
* @access public
- * @param mixed $file 上传文件
- * @param mixed $rule 验证规则
+ * @param mixed $file 上传文件
+ * @param mixed $rule 验证规则
* @return bool
*/
- public function fileMime($file, $rule)
+ public function fileMime($file, $rule): bool
{
if (is_array($file)) {
foreach ($file as $item) {
- if (!($item instanceof File) || !$item->checkMime($rule)) {
+ if (!($item instanceof File) || !$this->checkMime($item, $rule)) {
return false;
}
}
return true;
} elseif ($file instanceof File) {
- return $file->checkMime($rule);
+ return $this->checkMime($file, $rule);
}
return false;
@@ -886,21 +1041,21 @@ class Validate
/**
* 验证上传文件大小
* @access public
- * @param mixed $file 上传文件
- * @param mixed $rule 验证规则
+ * @param mixed $file 上传文件
+ * @param mixed $rule 验证规则
* @return bool
*/
- public function fileSize($file, $rule)
+ public function fileSize($file, $rule): bool
{
if (is_array($file)) {
foreach ($file as $item) {
- if (!($item instanceof File) || !$item->checkSize($rule)) {
+ if (!($item instanceof File) || !$this->checkSize($item, $rule)) {
return false;
}
}
return true;
} elseif ($file instanceof File) {
- return $file->checkSize($rule);
+ return $this->checkSize($file, $rule);
}
return false;
@@ -909,11 +1064,11 @@ class Validate
/**
* 验证图片的宽高及类型
* @access public
- * @param mixed $file 上传文件
- * @param mixed $rule 验证规则
+ * @param mixed $file 上传文件
+ * @param mixed $rule 验证规则
* @return bool
*/
- public function image($file, $rule)
+ public function image($file, $rule): bool
{
if (!($file instanceof File)) {
return false;
@@ -922,13 +1077,13 @@ class Validate
if ($rule) {
$rule = explode(',', $rule);
- list($width, $height, $type) = getimagesize($file->getRealPath());
+ [$width, $height, $type] = getimagesize($file->getRealPath());
if (isset($rule[2])) {
$imageType = strtolower($rule[2]);
- if ('jpeg' == $imageType) {
- $imageType = 'jpg';
+ if ('jpg' == $imageType) {
+ $imageType = 'jpeg';
}
if (image_type_to_extension($type, false) != $imageType) {
@@ -936,7 +1091,7 @@ class Validate
}
}
- list($w, $h) = $rule;
+ [$w, $h] = $rule;
return $w == $width && $h == $height;
}
@@ -944,27 +1099,14 @@ class Validate
return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]);
}
- /**
- * 验证请求类型
- * @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @return bool
- */
- public function method($value, $rule)
- {
- $method = Container::get('request')->method();
- return strtoupper($rule) == $method;
- }
-
/**
* 验证时间和日期是否符合指定格式
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
* @return bool
*/
- public function dateFormat($value, $rule)
+ public function dateFormat($value, $rule): bool
{
$info = date_parse_from_format($rule, $value);
return 0 == $info['warning_count'] && 0 == $info['error_count'];
@@ -973,13 +1115,13 @@ class Validate
/**
* 验证是否唯一
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名
- * @param array $data 数据
- * @param string $field 验证字段名
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名
+ * @param array $data 数据
+ * @param string $field 验证字段名
* @return bool
*/
- public function unique($value, $rule, $data, $field)
+ public function unique($value, $rule, array $data = [], string $field = ''): bool
{
if (is_string($rule)) {
$rule = explode(',', $rule);
@@ -989,14 +1131,11 @@ class Validate
// 指定模型类
$db = new $rule[0];
} else {
- try {
- $db = Container::get('app')->model($rule[0]);
- } catch (ClassNotFoundException $e) {
- $db = Db::name($rule[0]);
- }
+ $db = $this->db->name($rule[0]);
}
- $key = isset($rule[1]) ? $rule[1] : $field;
+ $key = $rule[1] ?? $field;
+ $map = [];
if (strpos($key, '^')) {
// 支持多个字段验证
@@ -1029,35 +1168,22 @@ class Validate
return true;
}
- /**
- * 使用行为类验证
- * @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @param array $data 数据
- * @return mixed
- */
- public function behavior($value, $rule, $data)
- {
- return Container::get('hook')->exec($rule, $data);
- }
-
/**
* 使用filter_var方式验证
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
* @return bool
*/
- public function filter($value, $rule)
+ public function filter($value, $rule): bool
{
if (is_string($rule) && strpos($rule, ',')) {
- list($rule, $param) = explode(',', $rule);
+ [$rule, $param] = explode(',', $rule);
} elseif (is_array($rule)) {
- $param = isset($rule[1]) ? $rule[1] : null;
+ $param = $rule[1] ?? 0;
$rule = $rule[0];
} else {
- $param = null;
+ $param = 0;
}
return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param);
@@ -1066,14 +1192,14 @@ class Validate
/**
* 验证某个字段等于某个值的时候必须
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @param array $data 数据
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
* @return bool
*/
- public function requireIf($value, $rule, $data)
+ public function requireIf($value, $rule, array $data = []): bool
{
- list($field, $val) = explode(',', $rule);
+ [$field, $val] = explode(',', $rule);
if ($this->getDataValue($data, $field) == $val) {
return !empty($value) || '0' == $value;
@@ -1085,12 +1211,12 @@ class Validate
/**
* 通过回调方法验证某个字段是否必须
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @param array $data 数据
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
* @return bool
*/
- public function requireCallback($value, $rule, $data)
+ public function requireCallback($value, $rule, array $data = []): bool
{
$result = call_user_func_array([$this, $rule], [$value, $data]);
@@ -1104,12 +1230,12 @@ class Validate
/**
* 验证某个字段有值的情况下必须
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @param array $data 数据
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
* @return bool
*/
- public function requireWith($value, $rule, $data)
+ public function requireWith($value, $rule, array $data = []): bool
{
$val = $this->getDataValue($data, $rule);
@@ -1121,13 +1247,32 @@ class Validate
}
/**
- * 验证是否在范围内
+ * 验证某个字段没有值的情况下必须
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
* @return bool
*/
- public function in($value, $rule)
+ public function requireWithout($value, $rule, array $data = []): bool
+ {
+ $val = $this->getDataValue($data, $rule);
+
+ if (empty($val)) {
+ return !empty($value) || '0' == $value;
+ }
+
+ return true;
+ }
+
+ /**
+ * 验证是否在范围内
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function in($value, $rule): bool
{
return in_array($value, is_array($rule) ? $rule : explode(',', $rule));
}
@@ -1135,11 +1280,11 @@ class Validate
/**
* 验证是否不在某个范围
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
* @return bool
*/
- public function notIn($value, $rule)
+ public function notIn($value, $rule): bool
{
return !in_array($value, is_array($rule) ? $rule : explode(',', $rule));
}
@@ -1147,16 +1292,16 @@ class Validate
/**
* between验证数据
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
* @return bool
*/
- public function between($value, $rule)
+ public function between($value, $rule): bool
{
if (is_string($rule)) {
$rule = explode(',', $rule);
}
- list($min, $max) = $rule;
+ [$min, $max] = $rule;
return $value >= $min && $value <= $max;
}
@@ -1164,16 +1309,16 @@ class Validate
/**
* 使用notbetween验证数据
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
* @return bool
*/
- public function notBetween($value, $rule)
+ public function notBetween($value, $rule): bool
{
if (is_string($rule)) {
$rule = explode(',', $rule);
}
- list($min, $max) = $rule;
+ [$min, $max] = $rule;
return $value < $min || $value > $max;
}
@@ -1181,11 +1326,11 @@ class Validate
/**
* 验证数据长度
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
* @return bool
*/
- public function length($value, $rule)
+ public function length($value, $rule): bool
{
if (is_array($value)) {
$length = count($value);
@@ -1195,9 +1340,9 @@ class Validate
$length = mb_strlen((string) $value);
}
- if (strpos($rule, ',')) {
+ if (is_string($rule) && strpos($rule, ',')) {
// 长度区间
- list($min, $max) = explode(',', $rule);
+ [$min, $max] = explode(',', $rule);
return $length >= $min && $length <= $max;
}
@@ -1208,11 +1353,11 @@ class Validate
/**
* 验证数据最大长度
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
* @return bool
*/
- public function max($value, $rule)
+ public function max($value, $rule): bool
{
if (is_array($value)) {
$length = count($value);
@@ -1228,11 +1373,11 @@ class Validate
/**
* 验证数据最小长度
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
* @return bool
*/
- public function min($value, $rule)
+ public function min($value, $rule): bool
{
if (is_array($value)) {
$length = count($value);
@@ -1248,12 +1393,12 @@ class Validate
/**
* 验证日期
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @param array $data 数据
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
* @return bool
*/
- public function after($value, $rule, $data)
+ public function after($value, $rule, array $data = []): bool
{
return strtotime($value) >= strtotime($rule);
}
@@ -1261,39 +1406,39 @@ class Validate
/**
* 验证日期
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @param array $data 数据
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
* @return bool
*/
- public function before($value, $rule, $data)
+ public function before($value, $rule, array $data = []): bool
{
return strtotime($value) <= strtotime($rule);
}
/**
- * 验证日期字段
- * @access protected
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @param array $data 数据
+ * 验证日期
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
* @return bool
*/
- protected function afterWith($value, $rule, $data)
+ public function afterWith($value, $rule, array $data = []): bool
{
$rule = $this->getDataValue($data, $rule);
return !is_null($rule) && strtotime($value) >= strtotime($rule);
}
/**
- * 验证日期字段
- * @access protected
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @param array $data 数据
+ * 验证日期
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
* @return bool
*/
- protected function beforeWith($value, $rule, $data)
+ public function beforeWith($value, $rule, array $data = []): bool
{
$rule = $this->getDataValue($data, $rule);
return !is_null($rule) && strtotime($value) <= strtotime($rule);
@@ -1302,17 +1447,17 @@ class Validate
/**
* 验证有效期
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
* @return bool
*/
- public function expire($value, $rule)
+ public function expire($value, $rule): bool
{
if (is_string($rule)) {
$rule = explode(',', $rule);
}
- list($start, $end) = $rule;
+ [$start, $end] = $rule;
if (!is_numeric($start)) {
$start = strtotime($start);
@@ -1322,17 +1467,17 @@ class Validate
$end = strtotime($end);
}
- return $_SERVER['REQUEST_TIME'] >= $start && $_SERVER['REQUEST_TIME'] <= $end;
+ return time() >= $start && time() <= $end;
}
/**
* 验证IP许可
* @access public
- * @param string $value 字段值
- * @param mixed $rule 验证规则
- * @return mixed
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
*/
- public function allowIp($value, $rule)
+ public function allowIp($value, $rule): bool
{
return in_array($value, is_array($rule) ? $rule : explode(',', $rule));
}
@@ -1340,11 +1485,11 @@ class Validate
/**
* 验证IP禁用
* @access public
- * @param string $value 字段值
- * @param mixed $rule 验证规则
- * @return mixed
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
*/
- public function denyIp($value, $rule)
+ public function denyIp($value, $rule): bool
{
return !in_array($value, is_array($rule) ? $rule : explode(',', $rule));
}
@@ -1352,17 +1497,19 @@ class Validate
/**
* 使用正则验证数据
* @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则 正则规则或者预定义正则名
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则 正则规则或者预定义正则名
* @return bool
*/
- public function regex($value, $rule)
+ public function regex($value, $rule): bool
{
if (isset($this->regex[$rule])) {
$rule = $this->regex[$rule];
+ } elseif (isset($this->defaultRegex[$rule])) {
+ $rule = $this->defaultRegex[$rule];
}
- if (0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) {
+ if (is_string($rule) && 0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) {
// 不是正则表达式则两端补上/
$rule = '/^' . $rule . '$/';
}
@@ -1371,37 +1518,9 @@ class Validate
}
/**
- * 验证表单令牌
- * @access public
- * @param mixed $value 字段值
- * @param mixed $rule 验证规则
- * @param array $data 数据
- * @return bool
+ * 获取错误信息
+ * @return array|string
*/
- public function token($value, $rule, $data)
- {
- $rule = !empty($rule) ? $rule : '__token__';
- $session = Container::get('session');
-
- if (!isset($data[$rule]) || !$session->has($rule)) {
- // 令牌数据无效
- return false;
- }
-
- // 令牌验证
- if (isset($data[$rule]) && $session->get($rule) === $data[$rule]) {
- // 防止重复提交
- $session->delete($rule); // 验证完成销毁session
- return true;
- }
-
- // 开启TOKEN重置
- $session->delete($rule);
-
- return false;
- }
-
- // 获取错误信息
public function getError()
{
return $this->error;
@@ -1410,15 +1529,15 @@ class Validate
/**
* 获取数据值
* @access protected
- * @param array $data 数据
- * @param string $key 数据标识 支持多维
+ * @param array $data 数据
+ * @param string $key 数据标识 支持二维
* @return mixed
*/
- protected function getDataValue($data, $key)
+ protected function getDataValue(array $data, $key)
{
if (is_numeric($key)) {
$value = $key;
- } elseif (strpos($key, '.')) {
+ } elseif (is_string($key) && strpos($key, '.')) {
// 支持多维数组验证
foreach (explode('.', $key) as $key) {
if (!isset($data[$key])) {
@@ -1428,7 +1547,7 @@ class Validate
$value = $data = $data[$key];
}
} else {
- $value = isset($data[$key]) ? $data[$key] : null;
+ $value = $data[$key] ?? null;
}
return $value;
@@ -1437,90 +1556,123 @@ class Validate
/**
* 获取验证规则的错误提示信息
* @access protected
- * @param string $attribute 字段英文名
- * @param string $title 字段描述名
- * @param string $type 验证规则名称
- * @param mixed $rule 验证规则数据
- * @return string
+ * @param string $attribute 字段英文名
+ * @param string $title 字段描述名
+ * @param string $type 验证规则名称
+ * @param mixed $rule 验证规则数据
+ * @return string|array
*/
- protected function getRuleMsg($attribute, $title, $type, $rule)
+ protected function getRuleMsg(string $attribute, string $title, string $type, $rule)
{
- $lang = Container::get('lang');
-
if (isset($this->message[$attribute . '.' . $type])) {
$msg = $this->message[$attribute . '.' . $type];
} elseif (isset($this->message[$attribute][$type])) {
$msg = $this->message[$attribute][$type];
} elseif (isset($this->message[$attribute])) {
$msg = $this->message[$attribute];
- } elseif (isset(self::$typeMsg[$type])) {
- $msg = self::$typeMsg[$type];
+ } elseif (isset($this->typeMsg[$type])) {
+ $msg = $this->typeMsg[$type];
} elseif (0 === strpos($type, 'require')) {
- $msg = self::$typeMsg['require'];
+ $msg = $this->typeMsg['require'];
} else {
- $msg = $title . $lang->get('not conform to the rules');
+ $msg = $title . $this->lang->get('not conform to the rules');
}
- if (is_string($msg) && 0 === strpos($msg, '{%')) {
- $msg = $lang->get(substr($msg, 2, -1));
- } elseif ($lang->has($msg)) {
- $msg = $lang->get($msg);
+ if (is_array($msg)) {
+ return $this->errorMsgIsArray($msg, $rule, $title);
}
- if (is_string($msg) && is_scalar($rule) && false !== strpos($msg, ':')) {
+ return $this->parseErrorMsg($msg, $rule, $title);
+ }
+
+ /**
+ * 获取验证规则的错误提示信息
+ * @access protected
+ * @param string $msg 错误信息
+ * @param mixed $rule 验证规则数据
+ * @param string $title 字段描述名
+ * @return string|array
+ */
+ protected function parseErrorMsg(string $msg, $rule, string $title)
+ {
+ if (0 === strpos($msg, '{%')) {
+ $msg = $this->lang->get(substr($msg, 2, -1));
+ } elseif ($this->lang->has($msg)) {
+ $msg = $this->lang->get($msg);
+ }
+
+ if (is_array($msg)) {
+ return $this->errorMsgIsArray($msg, $rule, $title);
+ }
+
+ // rule若是数组则转为字符串
+ if (is_array($rule)) {
+ $rule = implode(',', $rule);
+ }
+
+ if (is_scalar($rule) && false !== strpos($msg, ':')) {
// 变量替换
if (is_string($rule) && strpos($rule, ',')) {
$array = array_pad(explode(',', $rule), 3, '');
} else {
$array = array_pad([], 3, '');
}
+
$msg = str_replace(
- [':attribute', ':rule', ':1', ':2', ':3'],
- [$title, (string) $rule, $array[0], $array[1], $array[2]],
- $msg);
+ [':attribute', ':1', ':2', ':3'],
+ [$title, $array[0], $array[1], $array[2]],
+ $msg
+ );
+
+ if (strpos($msg, ':rule')) {
+ $msg = str_replace(':rule', (string) $rule, $msg);
+ }
}
return $msg;
}
+ /**
+ * 错误信息数组处理
+ * @access protected
+ * @param array $msg 错误信息
+ * @param mixed $rule 验证规则数据
+ * @param string $title 字段描述名
+ * @return array
+ */
+ protected function errorMsgIsArray(array $msg, $rule, string $title)
+ {
+ foreach ($msg as $key => $val) {
+ if (is_string($val)) {
+ $msg[$key] = $this->parseErrorMsg($val, $rule, $title);
+ }
+ }
+ return $msg;
+ }
+
/**
* 获取数据验证的场景
* @access protected
- * @param string $scene 验证场景
+ * @param string $scene 验证场景
* @return void
*/
- protected function getScene($scene = '')
+ protected function getScene(string $scene): void
{
- if (empty($scene)) {
- // 读取指定场景
- $scene = $this->currentScene;
- }
-
- if (empty($scene)) {
- return;
- }
-
$this->only = $this->append = $this->remove = [];
if (method_exists($this, 'scene' . $scene)) {
call_user_func([$this, 'scene' . $scene]);
} elseif (isset($this->scene[$scene])) {
// 如果设置了验证适用场景
- $scene = $this->scene[$scene];
-
- if (is_string($scene)) {
- $scene = explode(',', $scene);
- }
-
- $this->only = $scene;
+ $this->only = $this->scene[$scene];
}
}
/**
* 动态方法 直接调用is方法进行验证
* @access public
- * @param string $method 方法名
- * @param array $args 调用参数
+ * @param string $method 方法名
+ * @param array $args 调用参数
* @return bool
*/
public function __call($method, $args)
diff --git a/vendor/topthink/framework/src/think/View.php b/vendor/topthink/framework/src/think/View.php
new file mode 100644
index 000000000..2e7108840
--- /dev/null
+++ b/vendor/topthink/framework/src/think/View.php
@@ -0,0 +1,191 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use think\helper\Arr;
+
+/**
+ * 视图类
+ * @package think
+ */
+class View extends Manager
+{
+
+ protected $namespace = '\\think\\view\\driver\\';
+
+ /**
+ * 模板变量
+ * @var array
+ */
+ protected $data = [];
+
+ /**
+ * 内容过滤
+ * @var mixed
+ */
+ protected $filter;
+
+ /**
+ * 获取模板引擎
+ * @access public
+ * @param string $type 模板引擎类型
+ * @return $this
+ */
+ public function engine(string $type = null)
+ {
+ return $this->driver($type);
+ }
+
+ /**
+ * 模板变量赋值
+ * @access public
+ * @param string|array $name 模板变量
+ * @param mixed $value 变量值
+ * @return $this
+ */
+ public function assign($name, $value = null)
+ {
+ if (is_array($name)) {
+ $this->data = array_merge($this->data, $name);
+ } else {
+ $this->data[$name] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 视图过滤
+ * @access public
+ * @param Callable $filter 过滤方法或闭包
+ * @return $this
+ */
+ public function filter(callable $filter = null)
+ {
+ $this->filter = $filter;
+ return $this;
+ }
+
+ /**
+ * 解析和获取模板内容 用于输出
+ * @access public
+ * @param string $template 模板文件名或者内容
+ * @param array $vars 模板变量
+ * @return string
+ * @throws \Exception
+ */
+ public function fetch(string $template = '', array $vars = []): string
+ {
+ return $this->getContent(function () use ($vars, $template) {
+ $this->engine()->fetch($template, array_merge($this->data, $vars));
+ });
+ }
+
+ /**
+ * 渲染内容输出
+ * @access public
+ * @param string $content 内容
+ * @param array $vars 模板变量
+ * @return string
+ */
+ public function display(string $content, array $vars = []): string
+ {
+ return $this->getContent(function () use ($vars, $content) {
+ $this->engine()->display($content, array_merge($this->data, $vars));
+ });
+ }
+
+ /**
+ * 获取模板引擎渲染内容
+ * @param $callback
+ * @return string
+ * @throws \Exception
+ */
+ protected function getContent($callback): string
+ {
+ // 页面缓存
+ ob_start();
+ if (PHP_VERSION > 8.0) {
+ ob_implicit_flush(false);
+ } else {
+ ob_implicit_flush(0);
+ }
+
+ // 渲染输出
+ try {
+ $callback();
+ } catch (\Exception $e) {
+ ob_end_clean();
+ throw $e;
+ }
+
+ // 获取并清空缓存
+ $content = ob_get_clean();
+
+ if ($this->filter) {
+ $content = call_user_func_array($this->filter, [$content]);
+ }
+
+ return $content;
+ }
+
+ /**
+ * 模板变量赋值
+ * @access public
+ * @param string $name 变量名
+ * @param mixed $value 变量值
+ */
+ public function __set($name, $value)
+ {
+ $this->data[$name] = $value;
+ }
+
+ /**
+ * 取得模板显示变量的值
+ * @access protected
+ * @param string $name 模板变量
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return $this->data[$name];
+ }
+
+ /**
+ * 检测模板变量是否设置
+ * @access public
+ * @param string $name 模板变量名
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ return isset($this->data[$name]);
+ }
+
+ protected function resolveConfig(string $name)
+ {
+ $config = $this->app->config->get('view', []);
+ Arr::forget($config, 'type');
+ return $config;
+ }
+
+ /**
+ * 默认驱动
+ * @return string|null
+ */
+ public function getDefaultDriver()
+ {
+ return $this->app->config->get('view.type', 'php');
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/cache/Driver.php b/vendor/topthink/framework/src/think/cache/Driver.php
new file mode 100644
index 000000000..5813c7b34
--- /dev/null
+++ b/vendor/topthink/framework/src/think/cache/Driver.php
@@ -0,0 +1,357 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache;
+
+use Closure;
+use DateInterval;
+use DateTime;
+use DateTimeInterface;
+use Exception;
+use Psr\SimpleCache\CacheInterface;
+use think\Container;
+use think\contract\CacheHandlerInterface;
+use think\exception\InvalidArgumentException;
+use throwable;
+
+/**
+ * 缓存基础类
+ */
+abstract class Driver implements CacheInterface, CacheHandlerInterface
+{
+ /**
+ * 驱动句柄
+ * @var object
+ */
+ protected $handler = null;
+
+ /**
+ * 缓存读取次数
+ * @var integer
+ */
+ protected $readTimes = 0;
+
+ /**
+ * 缓存写入次数
+ * @var integer
+ */
+ protected $writeTimes = 0;
+
+ /**
+ * 缓存参数
+ * @var array
+ */
+ protected $options = [];
+
+ /**
+ * 缓存标签
+ * @var array
+ */
+ protected $tag = [];
+
+ /**
+ * 获取有效期
+ * @access protected
+ * @param integer|DateTimeInterface|DateInterval $expire 有效期
+ * @return int
+ */
+ protected function getExpireTime($expire): int
+ {
+ if ($expire instanceof DateTimeInterface) {
+ $expire = $expire->getTimestamp() - time();
+ } elseif ($expire instanceof DateInterval) {
+ $expire = DateTime::createFromFormat('U', (string) time())
+ ->add($expire)
+ ->format('U') - time();
+ }
+
+ return (int) $expire;
+ }
+
+ /**
+ * 获取实际的缓存标识
+ * @access public
+ * @param string $name 缓存名
+ * @return string
+ */
+ public function getCacheKey(string $name): string
+ {
+ return $this->options['prefix'] . $name;
+ }
+
+ /**
+ * 读取缓存并删除
+ * @access public
+ * @param string $name 缓存变量名
+ * @return mixed
+ */
+ public function pull(string $name)
+ {
+ $result = $this->get($name, false);
+
+ if ($result) {
+ $this->delete($name);
+ return $result;
+ }
+ }
+
+ /**
+ * 追加(数组)缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @return void
+ */
+ public function push(string $name, $value): void
+ {
+ $item = $this->get($name, []);
+
+ if (!is_array($item)) {
+ throw new InvalidArgumentException('only array cache can be push');
+ }
+
+ $item[] = $value;
+
+ if (count($item) > 1000) {
+ array_shift($item);
+ }
+
+ $item = array_unique($item);
+
+ $this->set($name, $item);
+ }
+
+ /**
+ * 追加TagSet数据
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @return void
+ */
+ public function append(string $name, $value): void
+ {
+ $this->push($name, $value);
+ }
+
+ /**
+ * 如果不存在则写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param int $expire 有效时间 0为永久
+ * @return mixed
+ */
+ public function remember(string $name, $value, $expire = null)
+ {
+ if ($this->has($name)) {
+ return $this->get($name);
+ }
+
+ $time = time();
+
+ while ($time + 5 > time() && $this->has($name . '_lock')) {
+ // 存在锁定则等待
+ usleep(200000);
+ }
+
+ try {
+ // 锁定
+ $this->set($name . '_lock', true);
+
+ if ($value instanceof Closure) {
+ // 获取缓存数据
+ $value = Container::getInstance()->invokeFunction($value);
+ }
+
+ // 缓存数据
+ $this->set($name, $value, $expire);
+
+ // 解锁
+ $this->delete($name . '_lock');
+ } catch (Exception | throwable $e) {
+ $this->delete($name . '_lock');
+ throw $e;
+ }
+
+ return $value;
+ }
+
+ /**
+ * 缓存标签
+ * @access public
+ * @param string|array $name 标签名
+ * @return TagSet
+ */
+ public function tag($name): TagSet
+ {
+ $name = (array) $name;
+ $key = implode('-', $name);
+
+ if (!isset($this->tag[$key])) {
+ $this->tag[$key] = new TagSet($name, $this);
+ }
+
+ return $this->tag[$key];
+ }
+
+ /**
+ * 获取标签包含的缓存标识
+ * @access public
+ * @param string $tag 标签标识
+ * @return array
+ */
+ public function getTagItems(string $tag): array
+ {
+ $name = $this->getTagKey($tag);
+ return $this->get($name, []);
+ }
+
+ /**
+ * 获取实际标签名
+ * @access public
+ * @param string $tag 标签名
+ * @return string
+ */
+ public function getTagKey(string $tag): string
+ {
+ return $this->options['tag_prefix'] . md5($tag);
+ }
+
+ /**
+ * 序列化数据
+ * @access protected
+ * @param mixed $data 缓存数据
+ * @return string
+ */
+ protected function serialize($data): string
+ {
+ if (is_numeric($data)) {
+ return (string) $data;
+ }
+
+ $serialize = $this->options['serialize'][0] ?? "serialize";
+
+ return $serialize($data);
+ }
+
+ /**
+ * 反序列化数据
+ * @access protected
+ * @param string $data 缓存数据
+ * @return mixed
+ */
+ protected function unserialize(string $data)
+ {
+ if (is_numeric($data)) {
+ return $data;
+ }
+
+ $unserialize = $this->options['serialize'][1] ?? "unserialize";
+
+ return $unserialize($data);
+ }
+
+ /**
+ * 返回句柄对象,可执行其它高级方法
+ *
+ * @access public
+ * @return object
+ */
+ public function handler()
+ {
+ return $this->handler;
+ }
+
+ /**
+ * 返回缓存读取次数
+ * @access public
+ * @return int
+ */
+ public function getReadTimes(): int
+ {
+ return $this->readTimes;
+ }
+
+ /**
+ * 返回缓存写入次数
+ * @access public
+ * @return int
+ */
+ public function getWriteTimes(): int
+ {
+ return $this->writeTimes;
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param iterable $keys 缓存变量名
+ * @param mixed $default 默认值
+ * @return iterable
+ * @throws InvalidArgumentException
+ */
+ public function getMultiple($keys, $default = null): iterable
+ {
+ $result = [];
+
+ foreach ($keys as $key) {
+ $result[$key] = $this->get($key, $default);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param iterable $values 缓存数据
+ * @param null|int|\DateInterval $ttl 有效时间 0为永久
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null): bool
+ {
+ foreach ($values as $key => $val) {
+ $result = $this->set($key, $val, $ttl);
+
+ if (false === $result) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param iterable $keys 缓存变量名
+ * @return bool
+ * @throws InvalidArgumentException
+ */
+ public function deleteMultiple($keys): bool
+ {
+ foreach ($keys as $key) {
+ $result = $this->delete($key);
+
+ if (false === $result) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public function __call($method, $args)
+ {
+ return call_user_func_array([$this->handler, $method], $args);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/cache/TagSet.php b/vendor/topthink/framework/src/think/cache/TagSet.php
new file mode 100644
index 000000000..5ba20769a
--- /dev/null
+++ b/vendor/topthink/framework/src/think/cache/TagSet.php
@@ -0,0 +1,132 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache;
+
+/**
+ * 标签集合
+ */
+class TagSet
+{
+ /**
+ * 标签的缓存Key
+ * @var array
+ */
+ protected $tag;
+
+ /**
+ * 缓存句柄
+ * @var Driver
+ */
+ protected $handler;
+
+ /**
+ * 架构函数
+ * @access public
+ * @param array $tag 缓存标签
+ * @param Driver $cache 缓存对象
+ */
+ public function __construct(array $tag, Driver $cache)
+ {
+ $this->tag = $tag;
+ $this->handler = $cache;
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer|\DateTime $expire 有效时间(秒)
+ * @return bool
+ */
+ public function set(string $name, $value, $expire = null): bool
+ {
+ $this->handler->set($name, $value, $expire);
+
+ $this->append($name);
+
+ return true;
+ }
+
+ /**
+ * 追加缓存标识到标签
+ * @access public
+ * @param string $name 缓存变量名
+ * @return void
+ */
+ public function append(string $name): void
+ {
+ $name = $this->handler->getCacheKey($name);
+
+ foreach ($this->tag as $tag) {
+ $key = $this->handler->getTagKey($tag);
+ $this->handler->append($key, $name);
+ }
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param iterable $values 缓存数据
+ * @param null|int|\DateInterval $ttl 有效时间 0为永久
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null): bool
+ {
+ foreach ($values as $key => $val) {
+ $result = $this->set($key, $val, $ttl);
+
+ if (false === $result) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 如果不存在则写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param int $expire 有效时间 0为永久
+ * @return mixed
+ */
+ public function remember(string $name, $value, $expire = null)
+ {
+ $result = $this->handler->remember($name, $value, $expire);
+
+ $this->append($name);
+
+ return $result;
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return bool
+ */
+ public function clear(): bool
+ {
+ // 指定标签清除
+ foreach ($this->tag as $tag) {
+ $names = $this->handler->getTagItems($tag);
+ $this->handler->clearTag($names);
+
+ $key = $this->handler->getTagKey($tag);
+ $this->handler->delete($key);
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/cache/driver/File.php b/vendor/topthink/framework/src/think/cache/driver/File.php
new file mode 100644
index 000000000..b36b06965
--- /dev/null
+++ b/vendor/topthink/framework/src/think/cache/driver/File.php
@@ -0,0 +1,304 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache\driver;
+
+use FilesystemIterator;
+use think\App;
+use think\cache\Driver;
+
+/**
+ * 文件缓存类
+ */
+class File extends Driver
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $options = [
+ 'expire' => 0,
+ 'cache_subdir' => true,
+ 'prefix' => '',
+ 'path' => '',
+ 'hash_type' => 'md5',
+ 'data_compress' => false,
+ 'tag_prefix' => 'tag:',
+ 'serialize' => [],
+ ];
+
+ /**
+ * 架构函数
+ * @param App $app
+ * @param array $options 参数
+ */
+ public function __construct(App $app, array $options = [])
+ {
+ if (!empty($options)) {
+ $this->options = array_merge($this->options, $options);
+ }
+
+ if (empty($this->options['path'])) {
+ $this->options['path'] = $app->getRuntimePath() . 'cache';
+ }
+
+ if (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) {
+ $this->options['path'] .= DIRECTORY_SEPARATOR;
+ }
+ }
+
+ /**
+ * 取得变量的存储文件名
+ * @access public
+ * @param string $name 缓存变量名
+ * @return string
+ */
+ public function getCacheKey(string $name): string
+ {
+ $name = hash($this->options['hash_type'], $name);
+
+ if ($this->options['cache_subdir']) {
+ // 使用子目录
+ $name = substr($name, 0, 2) . DIRECTORY_SEPARATOR . substr($name, 2);
+ }
+
+ if ($this->options['prefix']) {
+ $name = $this->options['prefix'] . DIRECTORY_SEPARATOR . $name;
+ }
+
+ return $this->options['path'] . $name . '.php';
+ }
+
+ /**
+ * 获取缓存数据
+ * @param string $name 缓存标识名
+ * @return array|null
+ */
+ protected function getRaw(string $name)
+ {
+ $filename = $this->getCacheKey($name);
+
+ if (!is_file($filename)) {
+ return;
+ }
+
+ $content = @file_get_contents($filename);
+
+ if (false !== $content) {
+ $expire = (int) substr($content, 8, 12);
+ if (0 != $expire && time() - $expire > filemtime($filename)) {
+ //缓存过期删除缓存文件
+ $this->unlink($filename);
+ return;
+ }
+
+ $content = substr($content, 32);
+
+ if ($this->options['data_compress'] && function_exists('gzcompress')) {
+ //启用数据压缩
+ $content = gzuncompress($content);
+ }
+
+ return is_string($content) ? ['content' => $content, 'expire' => $expire] : null;
+ }
+ }
+
+ /**
+ * 判断缓存是否存在
+ * @access public
+ * @param string $name 缓存变量名
+ * @return bool
+ */
+ public function has($name): bool
+ {
+ return $this->getRaw($name) !== null;
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get($name, $default = null)
+ {
+ $this->readTimes++;
+
+ $raw = $this->getRaw($name);
+
+ return is_null($raw) ? $default : $this->unserialize($raw['content']);
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param int|\DateTime $expire 有效时间 0为永久
+ * @return bool
+ */
+ public function set($name, $value, $expire = null): bool
+ {
+ $this->writeTimes++;
+
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+
+ $expire = $this->getExpireTime($expire);
+ $filename = $this->getCacheKey($name);
+
+ $dir = dirname($filename);
+
+ if (!is_dir($dir)) {
+ try {
+ mkdir($dir, 0755, true);
+ } catch (\Exception $e) {
+ // 创建失败
+ }
+ }
+
+ $data = $this->serialize($value);
+
+ if ($this->options['data_compress'] && function_exists('gzcompress')) {
+ //数据压缩
+ $data = gzcompress($data, 3);
+ }
+
+ $data = "\n" . $data;
+ $result = file_put_contents($filename, $data);
+
+ if ($result) {
+ clearstatcache();
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 自增缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function inc(string $name, int $step = 1)
+ {
+ if ($raw = $this->getRaw($name)) {
+ $value = $this->unserialize($raw['content']) + $step;
+ $expire = $raw['expire'];
+ } else {
+ $value = $step;
+ $expire = 0;
+ }
+
+ return $this->set($name, $value, $expire) ? $value : false;
+ }
+
+ /**
+ * 自减缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function dec(string $name, int $step = 1)
+ {
+ return $this->inc($name, -$step);
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return bool
+ */
+ public function delete($name): bool
+ {
+ $this->writeTimes++;
+
+ return $this->unlink($this->getCacheKey($name));
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return bool
+ */
+ public function clear(): bool
+ {
+ $this->writeTimes++;
+
+ $dirname = $this->options['path'] . $this->options['prefix'];
+
+ $this->rmdir($dirname);
+
+ return true;
+ }
+
+ /**
+ * 删除缓存标签
+ * @access public
+ * @param array $keys 缓存标识列表
+ * @return void
+ */
+ public function clearTag(array $keys): void
+ {
+ foreach ($keys as $key) {
+ $this->unlink($key);
+ }
+ }
+
+ /**
+ * 判断文件是否存在后,删除
+ * @access private
+ * @param string $path
+ * @return bool
+ */
+ private function unlink(string $path): bool
+ {
+ try {
+ return is_file($path) && unlink($path);
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * 删除文件夹
+ * @param $dirname
+ * @return bool
+ */
+ private function rmdir($dirname)
+ {
+ if (!is_dir($dirname)) {
+ return false;
+ }
+
+ $items = new FilesystemIterator($dirname);
+
+ foreach ($items as $item) {
+ if ($item->isDir() && !$item->isLink()) {
+ $this->rmdir($item->getPathname());
+ } else {
+ $this->unlink($item->getPathname());
+ }
+ }
+
+ @rmdir($dirname);
+
+ return true;
+ }
+
+}
diff --git a/thinkphp/library/think/cache/driver/Memcache.php b/vendor/topthink/framework/src/think/cache/driver/Memcache.php
old mode 100755
new mode 100644
similarity index 64%
rename from thinkphp/library/think/cache/driver/Memcache.php
rename to vendor/topthink/framework/src/think/cache/driver/Memcache.php
index 1c535597e..2fbbb9c72
--- a/thinkphp/library/think/cache/driver/Memcache.php
+++ b/vendor/topthink/framework/src/think/cache/driver/Memcache.php
@@ -2,19 +2,27 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\cache\driver;
use think\cache\Driver;
+/**
+ * Memcache缓存类
+ */
class Memcache extends Driver
{
+ /**
+ * 配置参数
+ * @var array
+ */
protected $options = [
'host' => '127.0.0.1',
'port' => 11211,
@@ -22,16 +30,17 @@ class Memcache extends Driver
'timeout' => 0, // 超时时间(单位:毫秒)
'persistent' => true,
'prefix' => '',
- 'serialize' => true,
+ 'tag_prefix' => 'tag:',
+ 'serialize' => [],
];
/**
* 架构函数
* @access public
- * @param array $options 缓存参数
+ * @param array $options 缓存参数
* @throws \BadFunctionCallException
*/
- public function __construct($options = [])
+ public function __construct(array $options = [])
{
if (!extension_loaded('memcache')) {
throw new \BadFunctionCallException('not support: memcache');
@@ -44,29 +53,29 @@ class Memcache extends Driver
$this->handler = new \Memcache;
// 支持集群
- $hosts = explode(',', $this->options['host']);
- $ports = explode(',', $this->options['port']);
+ $hosts = (array) $this->options['host'];
+ $ports = (array) $this->options['port'];
if (empty($ports[0])) {
$ports[0] = 11211;
}
// 建立连接
- foreach ((array) $hosts as $i => $host) {
- $port = isset($ports[$i]) ? $ports[$i] : $ports[0];
+ foreach ($hosts as $i => $host) {
+ $port = $ports[$i] ?? $ports[0];
$this->options['timeout'] > 0 ?
- $this->handler->addServer($host, $port, $this->options['persistent'], 1, $this->options['timeout']) :
- $this->handler->addServer($host, $port, $this->options['persistent'], 1);
+ $this->handler->addServer($host, (int) $port, $this->options['persistent'], 1, (int) $this->options['timeout']) :
+ $this->handler->addServer($host, (int) $port, $this->options['persistent'], 1);
}
}
/**
* 判断缓存
* @access public
- * @param string $name 缓存变量名
+ * @param string $name 缓存变量名
* @return bool
*/
- public function has($name)
+ public function has($name): bool
{
$key = $this->getCacheKey($name);
@@ -76,11 +85,11 @@ class Memcache extends Driver
/**
* 读取缓存
* @access public
- * @param string $name 缓存变量名
- * @param mixed $default 默认值
+ * @param string $name 缓存变量名
+ * @param mixed $default 默认值
* @return mixed
*/
- public function get($name, $default = false)
+ public function get($name, $default = null)
{
$this->readTimes++;
@@ -92,12 +101,12 @@ class Memcache extends Driver
/**
* 写入缓存
* @access public
- * @param string $name 缓存变量名
- * @param mixed $value 存储数据
- * @param int|DateTime $expire 有效时间(秒)
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param int|\DateTime $expire 有效时间(秒)
* @return bool
*/
- public function set($name, $value, $expire = null)
+ public function set($name, $value, $expire = null): bool
{
$this->writeTimes++;
@@ -105,16 +114,11 @@ class Memcache extends Driver
$expire = $this->options['expire'];
}
- if ($this->tag && !$this->has($name)) {
- $first = true;
- }
-
$key = $this->getCacheKey($name);
$expire = $this->getExpireTime($expire);
$value = $this->serialize($value);
if ($this->handler->set($key, $value, 0, $expire)) {
- isset($first) && $this->setTagItem($key);
return true;
}
@@ -124,11 +128,11 @@ class Memcache extends Driver
/**
* 自增缓存(针对数值缓存)
* @access public
- * @param string $name 缓存变量名
- * @param int $step 步长
+ * @param string $name 缓存变量名
+ * @param int $step 步长
* @return false|int
*/
- public function inc($name, $step = 1)
+ public function inc(string $name, int $step = 1)
{
$this->writeTimes++;
@@ -144,11 +148,11 @@ class Memcache extends Driver
/**
* 自减缓存(针对数值缓存)
* @access public
- * @param string $name 缓存变量名
- * @param int $step 步长
+ * @param string $name 缓存变量名
+ * @param int $step 步长
* @return false|int
*/
- public function dec($name, $step = 1)
+ public function dec(string $name, int $step = 1)
{
$this->writeTimes++;
@@ -162,11 +166,11 @@ class Memcache extends Driver
/**
* 删除缓存
* @access public
- * @param string $name 缓存变量名
- * @param bool|false $ttl
+ * @param string $name 缓存变量名
+ * @param bool|false $ttl
* @return bool
*/
- public function rm($name, $ttl = false)
+ public function delete($name, $ttl = false): bool
{
$this->writeTimes++;
@@ -180,27 +184,26 @@ class Memcache extends Driver
/**
* 清除缓存
* @access public
- * @param string $tag 标签名
* @return bool
*/
- public function clear($tag = null)
+ public function clear(): bool
{
- if ($tag) {
- // 指定标签清除
- $keys = $this->getTagItem($tag);
-
- foreach ($keys as $key) {
- $this->handler->delete($key);
- }
-
- $tagName = $this->getTagKey($tag);
- $this->rm($tagName);
- return true;
- }
-
$this->writeTimes++;
return $this->handler->flush();
}
+ /**
+ * 删除缓存标签
+ * @access public
+ * @param array $keys 缓存标识列表
+ * @return void
+ */
+ public function clearTag(array $keys): void
+ {
+ foreach ($keys as $key) {
+ $this->handler->delete($key);
+ }
+ }
+
}
diff --git a/thinkphp/library/think/cache/driver/Memcached.php b/vendor/topthink/framework/src/think/cache/driver/Memcached.php
old mode 100755
new mode 100644
similarity index 53%
rename from thinkphp/library/think/cache/driver/Memcached.php
rename to vendor/topthink/framework/src/think/cache/driver/Memcached.php
index 6af60d19b..71edb058f
--- a/thinkphp/library/think/cache/driver/Memcached.php
+++ b/vendor/topthink/framework/src/think/cache/driver/Memcached.php
@@ -2,37 +2,46 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\cache\driver;
use think\cache\Driver;
+/**
+ * Memcached缓存类
+ */
class Memcached extends Driver
{
+ /**
+ * 配置参数
+ * @var array
+ */
protected $options = [
- 'host' => '127.0.0.1',
- 'port' => 11211,
- 'expire' => 0,
- 'timeout' => 0, // 超时时间(单位:毫秒)
- 'prefix' => '',
- 'username' => '', //账号
- 'password' => '', //密码
- 'option' => [],
- 'serialize' => true,
+ 'host' => '127.0.0.1',
+ 'port' => 11211,
+ 'expire' => 0,
+ 'timeout' => 0, // 超时时间(单位:毫秒)
+ 'prefix' => '',
+ 'username' => '', //账号
+ 'password' => '', //密码
+ 'option' => [],
+ 'tag_prefix' => 'tag:',
+ 'serialize' => [],
];
/**
* 架构函数
* @access public
- * @param array $options 缓存参数
+ * @param array $options 缓存参数
*/
- public function __construct($options = [])
+ public function __construct(array $options = [])
{
if (!extension_loaded('memcached')) {
throw new \BadFunctionCallException('not support: memcached');
@@ -54,16 +63,16 @@ class Memcached extends Driver
}
// 支持集群
- $hosts = explode(',', $this->options['host']);
- $ports = explode(',', $this->options['port']);
+ $hosts = (array) $this->options['host'];
+ $ports = (array) $this->options['port'];
if (empty($ports[0])) {
$ports[0] = 11211;
}
// 建立连接
$servers = [];
- foreach ((array) $hosts as $i => $host) {
- $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1];
+ foreach ($hosts as $i => $host) {
+ $servers[] = [$host, $ports[$i] ?? $ports[0], 1];
}
$this->handler->addServers($servers);
@@ -77,10 +86,10 @@ class Memcached extends Driver
/**
* 判断缓存
* @access public
- * @param string $name 缓存变量名
+ * @param string $name 缓存变量名
* @return bool
*/
- public function has($name)
+ public function has($name): bool
{
$key = $this->getCacheKey($name);
@@ -90,11 +99,11 @@ class Memcached extends Driver
/**
* 读取缓存
* @access public
- * @param string $name 缓存变量名
- * @param mixed $default 默认值
+ * @param string $name 缓存变量名
+ * @param mixed $default 默认值
* @return mixed
*/
- public function get($name, $default = false)
+ public function get($name, $default = null)
{
$this->readTimes++;
@@ -106,12 +115,12 @@ class Memcached extends Driver
/**
* 写入缓存
* @access public
- * @param string $name 缓存变量名
- * @param mixed $value 存储数据
- * @param integer|\DateTime $expire 有效时间(秒)
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer|\DateTime $expire 有效时间(秒)
* @return bool
*/
- public function set($name, $value, $expire = null)
+ public function set($name, $value, $expire = null): bool
{
$this->writeTimes++;
@@ -119,16 +128,11 @@ class Memcached extends Driver
$expire = $this->options['expire'];
}
- if ($this->tag && !$this->has($name)) {
- $first = true;
- }
-
$key = $this->getCacheKey($name);
$expire = $this->getExpireTime($expire);
$value = $this->serialize($value);
if ($this->handler->set($key, $value, $expire)) {
- isset($first) && $this->setTagItem($key);
return true;
}
@@ -138,11 +142,11 @@ class Memcached extends Driver
/**
* 自增缓存(针对数值缓存)
* @access public
- * @param string $name 缓存变量名
- * @param int $step 步长
+ * @param string $name 缓存变量名
+ * @param int $step 步长
* @return false|int
*/
- public function inc($name, $step = 1)
+ public function inc(string $name, int $step = 1)
{
$this->writeTimes++;
@@ -158,11 +162,11 @@ class Memcached extends Driver
/**
* 自减缓存(针对数值缓存)
* @access public
- * @param string $name 缓存变量名
- * @param int $step 步长
+ * @param string $name 缓存变量名
+ * @param int $step 步长
* @return false|int
*/
- public function dec($name, $step = 1)
+ public function dec(string $name, int $step = 1)
{
$this->writeTimes++;
@@ -176,11 +180,11 @@ class Memcached extends Driver
/**
* 删除缓存
* @access public
- * @param string $name 缓存变量名
- * @param bool|false $ttl
+ * @param string $name 缓存变量名
+ * @param bool|false $ttl
* @return bool
*/
- public function rm($name, $ttl = false)
+ public function delete($name, $ttl = false): bool
{
$this->writeTimes++;
@@ -194,86 +198,24 @@ class Memcached extends Driver
/**
* 清除缓存
* @access public
- * @param string $tag 标签名
* @return bool
*/
- public function clear($tag = null)
+ public function clear(): bool
{
- if ($tag) {
- // 指定标签清除
- $keys = $this->getTagItem($tag);
-
- $this->handler->deleteMulti($keys);
- $this->rm($this->getTagKey($tag));
-
- return true;
- }
-
$this->writeTimes++;
return $this->handler->flush();
}
/**
- * 缓存标签
+ * 删除缓存标签
* @access public
- * @param string $name 标签名
- * @param string|array $keys 缓存标识
- * @param bool $overlay 是否覆盖
- * @return $this
- */
- public function tag($name, $keys = null, $overlay = false)
- {
- if (is_null($keys)) {
- $this->tag = $name;
- } else {
- $tagName = $this->getTagKey($name);
- if ($overlay) {
- $this->handler->delete($tagName);
- }
-
- if (!$this->handler->has($tagName)) {
- $this->handler->set($tagName, '');
- }
-
- foreach ($keys as $key) {
- $this->handler->append($tagName, ',' . $key);
- }
- }
-
- return $this;
- }
-
- /**
- * 更新标签
- * @access protected
- * @param string $name 缓存标识
+ * @param array $keys 缓存标识列表
* @return void
*/
- protected function setTagItem($name)
+ public function clearTag(array $keys): void
{
- if ($this->tag) {
- $tagName = $this->getTagKey($this->tag);
-
- if ($this->handler->has($tagName)) {
- $this->handler->append($tagName, ',' . $name);
- } else {
- $this->handler->set($tagName, $name);
- }
-
- $this->tag = null;
- }
+ $this->handler->deleteMulti($keys);
}
- /**
- * 获取标签包含的缓存标识
- * @access public
- * @param string $tag 缓存标签
- * @return array
- */
- public function getTagItem($tag)
- {
- $tagName = $this->getTagKey($tag);
- return explode(',', trim($this->handler->get($tagName), ','));
- }
}
diff --git a/thinkphp/library/think/cache/driver/Redis.php b/vendor/topthink/framework/src/think/cache/driver/Redis.php
old mode 100755
new mode 100644
similarity index 55%
rename from thinkphp/library/think/cache/driver/Redis.php
rename to vendor/topthink/framework/src/think/cache/driver/Redis.php
index 813746e77..791b27b88
--- a/thinkphp/library/think/cache/driver/Redis.php
+++ b/vendor/topthink/framework/src/think/cache/driver/Redis.php
@@ -2,12 +2,13 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\cache\driver;
@@ -22,6 +23,13 @@ use think\cache\Driver;
*/
class Redis extends Driver
{
+ /** @var \Predis\Client|\Redis */
+ protected $handler;
+
+ /**
+ * 配置参数
+ * @var array
+ */
protected $options = [
'host' => '127.0.0.1',
'port' => 6379,
@@ -31,15 +39,16 @@ class Redis extends Driver
'expire' => 0,
'persistent' => false,
'prefix' => '',
- 'serialize' => true,
+ 'tag_prefix' => 'tag:',
+ 'serialize' => [],
];
/**
* 架构函数
* @access public
- * @param array $options 缓存参数
+ * @param array $options 缓存参数
*/
- public function __construct($options = [])
+ public function __construct(array $options = [])
{
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
@@ -49,18 +58,14 @@ class Redis extends Driver
$this->handler = new \Redis;
if ($this->options['persistent']) {
- $this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']);
+ $this->handler->pconnect($this->options['host'], (int) $this->options['port'], (int) $this->options['timeout'], 'persistent_id_' . $this->options['select']);
} else {
- $this->handler->connect($this->options['host'], $this->options['port'], $this->options['timeout']);
+ $this->handler->connect($this->options['host'], (int) $this->options['port'], (int) $this->options['timeout']);
}
if ('' != $this->options['password']) {
$this->handler->auth($this->options['password']);
}
-
- if (0 != $this->options['select']) {
- $this->handler->select($this->options['select']);
- }
} elseif (class_exists('\Predis\Client')) {
$params = [];
foreach ($this->options as $key => $val) {
@@ -80,33 +85,37 @@ class Redis extends Driver
} else {
throw new \BadFunctionCallException('not support: redis');
}
+
+ if (0 != $this->options['select']) {
+ $this->handler->select((int) $this->options['select']);
+ }
}
/**
* 判断缓存
* @access public
- * @param string $name 缓存变量名
+ * @param string $name 缓存变量名
* @return bool
*/
- public function has($name)
+ public function has($name): bool
{
- return $this->handler->exists($this->getCacheKey($name));
+ return $this->handler->exists($this->getCacheKey($name)) ? true : false;
}
/**
* 读取缓存
* @access public
- * @param string $name 缓存变量名
- * @param mixed $default 默认值
+ * @param string $name 缓存变量名
+ * @param mixed $default 默认值
* @return mixed
*/
- public function get($name, $default = false)
+ public function get($name, $default = null)
{
$this->readTimes++;
+ $key = $this->getCacheKey($name);
+ $value = $this->handler->get($key);
- $value = $this->handler->get($this->getCacheKey($name));
-
- if (is_null($value) || false === $value) {
+ if (false === $value || is_null($value)) {
return $default;
}
@@ -116,12 +125,12 @@ class Redis extends Driver
/**
* 写入缓存
* @access public
- * @param string $name 缓存变量名
- * @param mixed $value 存储数据
- * @param integer|\DateTime $expire 有效时间(秒)
- * @return boolean
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer|\DateTime $expire 有效时间(秒)
+ * @return bool
*/
- public function set($name, $value, $expire = null)
+ public function set($name, $value, $expire = null): bool
{
$this->writeTimes++;
@@ -129,37 +138,29 @@ class Redis extends Driver
$expire = $this->options['expire'];
}
- if ($this->tag && !$this->has($name)) {
- $first = true;
- }
-
$key = $this->getCacheKey($name);
$expire = $this->getExpireTime($expire);
-
- $value = $this->serialize($value);
+ $value = $this->serialize($value);
if ($expire) {
- $result = $this->handler->setex($key, $expire, $value);
+ $this->handler->setex($key, $expire, $value);
} else {
- $result = $this->handler->set($key, $value);
+ $this->handler->set($key, $value);
}
- isset($first) && $this->setTagItem($key);
-
- return $result;
+ return true;
}
/**
* 自增缓存(针对数值缓存)
* @access public
- * @param string $name 缓存变量名
- * @param int $step 步长
+ * @param string $name 缓存变量名
+ * @param int $step 步长
* @return false|int
*/
- public function inc($name, $step = 1)
+ public function inc(string $name, int $step = 1)
{
$this->writeTimes++;
-
$key = $this->getCacheKey($name);
return $this->handler->incrby($key, $step);
@@ -168,14 +169,13 @@ class Redis extends Driver
/**
* 自减缓存(针对数值缓存)
* @access public
- * @param string $name 缓存变量名
- * @param int $step 步长
+ * @param string $name 缓存变量名
+ * @param int $step 步长
* @return false|int
*/
- public function dec($name, $step = 1)
+ public function dec(string $name, int $step = 1)
{
$this->writeTimes++;
-
$key = $this->getCacheKey($name);
return $this->handler->decrby($key, $step);
@@ -184,89 +184,66 @@ class Redis extends Driver
/**
* 删除缓存
* @access public
- * @param string $name 缓存变量名
- * @return boolean
+ * @param string $name 缓存变量名
+ * @return bool
*/
- public function rm($name)
+ public function delete($name): bool
{
$this->writeTimes++;
- return $this->handler->del($this->getCacheKey($name));
+ $key = $this->getCacheKey($name);
+ $result = $this->handler->del($key);
+ return $result > 0;
}
/**
* 清除缓存
* @access public
- * @param string $tag 标签名
- * @return boolean
+ * @return bool
*/
- public function clear($tag = null)
+ public function clear(): bool
{
- if ($tag) {
- // 指定标签清除
- $keys = $this->getTagItem($tag);
-
- $this->handler->del($keys);
-
- $tagName = $this->getTagKey($tag);
- $this->handler->del($tagName);
- return true;
- }
-
$this->writeTimes++;
-
- return $this->handler->flushDB();
+ $this->handler->flushDB();
+ return true;
}
/**
- * 缓存标签
+ * 删除缓存标签
* @access public
- * @param string $name 标签名
- * @param string|array $keys 缓存标识
- * @param bool $overlay 是否覆盖
- * @return $this
- */
- public function tag($name, $keys = null, $overlay = false)
- {
- if (is_null($keys)) {
- $this->tag = $name;
- } else {
- $tagName = $this->getTagKey($name);
- if ($overlay) {
- $this->handler->del($tagName);
- }
-
- foreach ($keys as $key) {
- $this->handler->sAdd($tagName, $key);
- }
- }
-
- return $this;
- }
-
- /**
- * 更新标签
- * @access protected
- * @param string $name 缓存标识
+ * @param array $keys 缓存标识列表
* @return void
*/
- protected function setTagItem($name)
+ public function clearTag(array $keys): void
{
- if ($this->tag) {
- $tagName = $this->getTagKey($this->tag);
- $this->handler->sAdd($tagName, $name);
- }
+ // 指定标签清除
+ $this->handler->del($keys);
+ }
+
+ /**
+ * 追加TagSet数据
+ * @access public
+ * @param string $name 缓存标识
+ * @param mixed $value 数据
+ * @return void
+ */
+ public function append(string $name, $value): void
+ {
+ $key = $this->getCacheKey($name);
+ $this->handler->sAdd($key, $value);
}
/**
* 获取标签包含的缓存标识
- * @access protected
- * @param string $tag 缓存标签
+ * @access public
+ * @param string $tag 缓存标签
* @return array
*/
- protected function getTagItem($tag)
+ public function getTagItems(string $tag): array
{
- $tagName = $this->getTagKey($tag);
- return $this->handler->sMembers($tagName);
+ $name = $this->getTagKey($tag);
+ $key = $this->getCacheKey($name);
+ return $this->handler->sMembers($key);
}
+
}
diff --git a/thinkphp/library/think/cache/driver/Wincache.php b/vendor/topthink/framework/src/think/cache/driver/Wincache.php
old mode 100755
new mode 100644
similarity index 64%
rename from thinkphp/library/think/cache/driver/Wincache.php
rename to vendor/topthink/framework/src/think/cache/driver/Wincache.php
index ef1578417..8b3e8b863
--- a/thinkphp/library/think/cache/driver/Wincache.php
+++ b/vendor/topthink/framework/src/think/cache/driver/Wincache.php
@@ -2,12 +2,13 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\cache\driver;
@@ -15,23 +16,27 @@ use think\cache\Driver;
/**
* Wincache缓存驱动
- * @author liu21st
*/
class Wincache extends Driver
{
+ /**
+ * 配置参数
+ * @var array
+ */
protected $options = [
- 'prefix' => '',
- 'expire' => 0,
- 'serialize' => true,
+ 'prefix' => '',
+ 'expire' => 0,
+ 'tag_prefix' => 'tag:',
+ 'serialize' => [],
];
/**
* 架构函数
* @access public
- * @param array $options 缓存参数
+ * @param array $options 缓存参数
* @throws \BadFunctionCallException
*/
- public function __construct($options = [])
+ public function __construct(array $options = [])
{
if (!function_exists('wincache_ucache_info')) {
throw new \BadFunctionCallException('not support: WinCache');
@@ -45,10 +50,10 @@ class Wincache extends Driver
/**
* 判断缓存
* @access public
- * @param string $name 缓存变量名
+ * @param string $name 缓存变量名
* @return bool
*/
- public function has($name)
+ public function has($name): bool
{
$this->readTimes++;
@@ -60,11 +65,11 @@ class Wincache extends Driver
/**
* 读取缓存
* @access public
- * @param string $name 缓存变量名
- * @param mixed $default 默认值
+ * @param string $name 缓存变量名
+ * @param mixed $default 默认值
* @return mixed
*/
- public function get($name, $default = false)
+ public function get($name, $default = null)
{
$this->readTimes++;
@@ -76,12 +81,12 @@ class Wincache extends Driver
/**
* 写入缓存
* @access public
- * @param string $name 缓存变量名
- * @param mixed $value 存储数据
- * @param integer|\DateTime $expire 有效时间(秒)
- * @return boolean
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer|\DateTime $expire 有效时间(秒)
+ * @return bool
*/
- public function set($name, $value, $expire = null)
+ public function set($name, $value, $expire = null): bool
{
$this->writeTimes++;
@@ -93,12 +98,7 @@ class Wincache extends Driver
$expire = $this->getExpireTime($expire);
$value = $this->serialize($value);
- if ($this->tag && !$this->has($name)) {
- $first = true;
- }
-
if (wincache_ucache_set($key, $value, $expire)) {
- isset($first) && $this->setTagItem($key);
return true;
}
@@ -108,11 +108,11 @@ class Wincache extends Driver
/**
* 自增缓存(针对数值缓存)
* @access public
- * @param string $name 缓存变量名
- * @param int $step 步长
+ * @param string $name 缓存变量名
+ * @param int $step 步长
* @return false|int
*/
- public function inc($name, $step = 1)
+ public function inc(string $name, int $step = 1)
{
$this->writeTimes++;
@@ -124,11 +124,11 @@ class Wincache extends Driver
/**
* 自减缓存(针对数值缓存)
* @access public
- * @param string $name 缓存变量名
- * @param int $step 步长
+ * @param string $name 缓存变量名
+ * @param int $step 步长
* @return false|int
*/
- public function dec($name, $step = 1)
+ public function dec(string $name, int $step = 1)
{
$this->writeTimes++;
@@ -140,10 +140,10 @@ class Wincache extends Driver
/**
* 删除缓存
* @access public
- * @param string $name 缓存变量名
- * @return boolean
+ * @param string $name 缓存变量名
+ * @return bool
*/
- public function rm($name)
+ public function delete($name): bool
{
$this->writeTimes++;
@@ -153,23 +153,23 @@ class Wincache extends Driver
/**
* 清除缓存
* @access public
- * @param string $tag 标签名
- * @return boolean
+ * @return bool
*/
- public function clear($tag = null)
+ public function clear(): bool
{
- if ($tag) {
- $keys = $this->getTagItem($tag);
-
- wincache_ucache_delete($keys);
-
- $tagName = $this->getTagkey($tag);
- $this->rm($tagName);
- return true;
- }
-
$this->writeTimes++;
return wincache_ucache_clear();
}
+ /**
+ * 删除缓存标签
+ * @access public
+ * @param array $keys 缓存标识列表
+ * @return void
+ */
+ public function clearTag(array $keys): void
+ {
+ wincache_ucache_delete($keys);
+ }
+
}
diff --git a/thinkphp/library/think/console/Command.php b/vendor/topthink/framework/src/think/console/Command.php
old mode 100755
new mode 100644
similarity index 68%
rename from thinkphp/library/think/console/Command.php
rename to vendor/topthink/framework/src/think/console/Command.php
index a208e7b54..bd3fb209b
--- a/thinkphp/library/think/console/Command.php
+++ b/vendor/topthink/framework/src/think/console/Command.php
@@ -8,20 +8,26 @@
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\console;
+use Exception;
+use InvalidArgumentException;
+use LogicException;
+use think\App;
use think\Console;
use think\console\input\Argument;
use think\console\input\Definition;
use think\console\input\Option;
-class Command
+abstract class Command
{
/** @var Console */
private $console;
private $name;
+ private $processTitle;
private $aliases = [];
private $definition;
private $help;
@@ -29,9 +35,8 @@ class Command
private $ignoreValidationErrors = false;
private $consoleDefinitionMerged = false;
private $consoleDefinitionMergedWithArgs = false;
- private $code;
- private $synopsis = [];
- private $usages = [];
+ private $synopsis = [];
+ private $usages = [];
/** @var Input */
protected $input;
@@ -39,31 +44,29 @@ class Command
/** @var Output */
protected $output;
+ /** @var App */
+ protected $app;
+
/**
* 构造方法
- * @param string|null $name 命令名称,如果没有设置则比如在 configure() 里设置
- * @throws \LogicException
+ * @throws LogicException
* @api
*/
- public function __construct($name = null)
+ public function __construct()
{
$this->definition = new Definition();
- if (null !== $name) {
- $this->setName($name);
- }
-
$this->configure();
if (!$this->name) {
- throw new \LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this)));
+ throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this)));
}
}
/**
* 忽略验证错误
*/
- public function ignoreValidationErrors()
+ public function ignoreValidationErrors(): void
{
$this->ignoreValidationErrors = true;
}
@@ -72,7 +75,7 @@ class Command
* 设置控制台
* @param Console $console
*/
- public function setConsole(Console $console = null)
+ public function setConsole(Console $console = null): void
{
$this->console = $console;
}
@@ -82,16 +85,34 @@ class Command
* @return Console
* @api
*/
- public function getConsole()
+ public function getConsole(): Console
{
return $this->console;
}
+ /**
+ * 设置app
+ * @param App $app
+ */
+ public function setApp(App $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * 获取app
+ * @return App
+ */
+ public function getApp()
+ {
+ return $this->app;
+ }
+
/**
* 是否有效
* @return bool
*/
- public function isEnabled()
+ public function isEnabled(): bool
{
return true;
}
@@ -108,12 +129,12 @@ class Command
* @param Input $input
* @param Output $output
* @return null|int
- * @throws \LogicException
+ * @throws LogicException
* @see setCode()
*/
protected function execute(Input $input, Output $output)
{
- throw new \LogicException('You must override the execute() method in the concrete command class.');
+ return $this->app->invoke([$this, 'handle']);
}
/**
@@ -139,11 +160,11 @@ class Command
* @param Input $input
* @param Output $output
* @return int
- * @throws \Exception
+ * @throws Exception
* @see setCode()
* @see execute()
*/
- public function run(Input $input, Output $output)
+ public function run(Input $input, Output $output): int
{
$this->input = $input;
$this->output = $output;
@@ -155,7 +176,7 @@ class Command
try {
$input->bind($this->definition);
- } catch (\Exception $e) {
+ } catch (Exception $e) {
if (!$this->ignoreValidationErrors) {
throw $e;
}
@@ -163,51 +184,39 @@ class Command
$this->initialize($input, $output);
+ if (null !== $this->processTitle) {
+ if (function_exists('cli_set_process_title')) {
+ if (false === @cli_set_process_title($this->processTitle)) {
+ if ('Darwin' === PHP_OS) {
+ $output->writeln('Running "cli_get_process_title" as an unprivileged user is not supported on MacOS. ');
+ } else {
+ $error = error_get_last();
+ trigger_error($error['message'], E_USER_WARNING);
+ }
+ }
+ } elseif (function_exists('setproctitle')) {
+ setproctitle($this->processTitle);
+ } elseif (Output::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
+ $output->writeln('Install the proctitle PECL to be able to change the process title. ');
+ }
+ }
+
if ($input->isInteractive()) {
$this->interact($input, $output);
}
$input->validate();
- if ($this->code) {
- $statusCode = call_user_func($this->code, $input, $output);
- } else {
- $statusCode = $this->execute($input, $output);
- }
+ $statusCode = $this->execute($input, $output);
return is_numeric($statusCode) ? (int) $statusCode : 0;
}
- /**
- * 设置执行代码
- * @param callable $code callable(InputInterface $input, OutputInterface $output)
- * @return Command
- * @throws \InvalidArgumentException
- * @see execute()
- */
- public function setCode(callable $code)
- {
- if (!is_callable($code)) {
- throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.');
- }
-
- if (PHP_VERSION_ID >= 50400 && $code instanceof \Closure) {
- $r = new \ReflectionFunction($code);
- if (null === $r->getClosureThis()) {
- $code = \Closure::bind($code, $this);
- }
- }
-
- $this->code = $code;
-
- return $this;
- }
-
/**
* 合并参数定义
* @param bool $mergeArgs
*/
- public function mergeConsoleDefinition($mergeArgs = true)
+ public function mergeConsoleDefinition(bool $mergeArgs = true)
{
if (null === $this->console
|| (true === $this->consoleDefinitionMerged
@@ -254,7 +263,7 @@ class Command
* @return Definition
* @api
*/
- public function getDefinition()
+ public function getDefinition(): Definition
{
return $this->definition;
}
@@ -263,7 +272,7 @@ class Command
* 获取当前指令的参数定义
* @return Definition
*/
- public function getNativeDefinition()
+ public function getNativeDefinition(): Definition
{
return $this->getDefinition();
}
@@ -276,7 +285,7 @@ class Command
* @param mixed $default 默认值
* @return Command
*/
- public function addArgument($name, $mode = null, $description = '', $default = null)
+ public function addArgument(string $name, int $mode = null, string $description = '', $default = null)
{
$this->definition->addArgument(new Argument($name, $mode, $description, $default));
@@ -292,7 +301,7 @@ class Command
* @param mixed $default 默认值
* @return Command
*/
- public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null)
+ public function addOption(string $name, string $shortcut = null, int $mode = null, string $description = '', $default = null)
{
$this->definition->addOption(new Option($name, $shortcut, $mode, $description, $default));
@@ -303,9 +312,9 @@ class Command
* 设置指令名称
* @param string $name
* @return Command
- * @throws \InvalidArgumentException
+ * @throws InvalidArgumentException
*/
- public function setName($name)
+ public function setName(string $name)
{
$this->validateName($name);
@@ -314,13 +323,29 @@ class Command
return $this;
}
+ /**
+ * 设置进程名称
+ *
+ * PHP 5.5+ or the proctitle PECL library is required
+ *
+ * @param string $title The process title
+ *
+ * @return $this
+ */
+ public function setProcessTitle($title)
+ {
+ $this->processTitle = $title;
+
+ return $this;
+ }
+
/**
* 获取指令名称
* @return string
*/
- public function getName()
+ public function getName(): string
{
- return $this->name;
+ return $this->name ?: '';
}
/**
@@ -328,7 +353,7 @@ class Command
* @param string $description
* @return Command
*/
- public function setDescription($description)
+ public function setDescription(string $description)
{
$this->description = $description;
@@ -339,9 +364,9 @@ class Command
* 获取描述
* @return string
*/
- public function getDescription()
+ public function getDescription(): string
{
- return $this->description;
+ return $this->description ?: '';
}
/**
@@ -349,7 +374,7 @@ class Command
* @param string $help
* @return Command
*/
- public function setHelp($help)
+ public function setHelp(string $help)
{
$this->help = $help;
@@ -360,16 +385,16 @@ class Command
* 获取帮助信息
* @return string
*/
- public function getHelp()
+ public function getHelp(): string
{
- return $this->help;
+ return $this->help ?: '';
}
/**
* 描述信息
* @return string
*/
- public function getProcessedHelp()
+ public function getProcessedHelp(): string
{
$name = $this->name;
@@ -389,14 +414,10 @@ class Command
* 设置别名
* @param string[] $aliases
* @return Command
- * @throws \InvalidArgumentException
+ * @throws InvalidArgumentException
*/
- public function setAliases($aliases)
+ public function setAliases(iterable $aliases)
{
- if (!is_array($aliases) && !$aliases instanceof \Traversable) {
- throw new \InvalidArgumentException('$aliases must be an array or an instance of \Traversable');
- }
-
foreach ($aliases as $alias) {
$this->validateName($alias);
}
@@ -410,7 +431,7 @@ class Command
* 获取别名
* @return array
*/
- public function getAliases()
+ public function getAliases(): array
{
return $this->aliases;
}
@@ -420,7 +441,7 @@ class Command
* @param bool $short 是否简单的
* @return string
*/
- public function getSynopsis($short = false)
+ public function getSynopsis(bool $short = false): string
{
$key = $short ? 'short' : 'long';
@@ -436,7 +457,7 @@ class Command
* @param string $usage
* @return $this
*/
- public function addUsage($usage)
+ public function addUsage(string $usage)
{
if (0 !== strpos($usage, $this->name)) {
$usage = sprintf('%s %s', $this->name, $usage);
@@ -451,7 +472,7 @@ class Command
* 获取用法介绍
* @return array
*/
- public function getUsages()
+ public function getUsages(): array
{
return $this->usages;
}
@@ -459,12 +480,12 @@ class Command
/**
* 验证指令名称
* @param string $name
- * @throws \InvalidArgumentException
+ * @throws InvalidArgumentException
*/
- private function validateName($name)
+ private function validateName(string $name)
{
if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
- throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
+ throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
}
}
@@ -473,10 +494,11 @@ class Command
* @param Table $table
* @return string
*/
- protected function table(Table $table)
+ protected function table(Table $table): string
{
$content = $table->render();
$this->output->writeln($content);
return $content;
}
+
}
diff --git a/thinkphp/library/think/console/Input.php b/vendor/topthink/framework/src/think/console/Input.php
old mode 100755
new mode 100644
similarity index 89%
rename from thinkphp/library/think/console/Input.php
rename to vendor/topthink/framework/src/think/console/Input.php
index 2482dfdc0..9ae907758
--- a/thinkphp/library/think/console/Input.php
+++ b/vendor/topthink/framework/src/think/console/Input.php
@@ -8,6 +8,7 @@
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\console;
@@ -60,7 +61,7 @@ class Input
* 绑定实例
* @param Definition $definition A InputDefinition instance
*/
- public function bind(Definition $definition)
+ public function bind(Definition $definition): void
{
$this->arguments = [];
$this->options = [];
@@ -72,7 +73,7 @@ class Input
/**
* 解析参数
*/
- protected function parse()
+ protected function parse(): void
{
$parseOptions = true;
$this->parsed = $this->tokens;
@@ -95,7 +96,7 @@ class Input
* 解析短选项
* @param string $token 当前的指令.
*/
- private function parseShortOption($token)
+ private function parseShortOption(string $token): void
{
$name = substr($token, 1);
@@ -117,7 +118,7 @@ class Input
* @param string $name 当前指令
* @throws \RuntimeException
*/
- private function parseShortOptionSet($name)
+ private function parseShortOptionSet(string $name): void
{
$len = strlen($name);
for ($i = 0; $i < $len; ++$i) {
@@ -140,7 +141,7 @@ class Input
* 解析完整选项
* @param string $token 当前指令
*/
- private function parseLongOption($token)
+ private function parseLongOption(string $token): void
{
$name = substr($token, 2);
@@ -156,7 +157,7 @@ class Input
* @param string $token 当前指令
* @throws \RuntimeException
*/
- private function parseArgument($token)
+ private function parseArgument(string $token): void
{
$c = count($this->arguments);
@@ -180,7 +181,7 @@ class Input
* @param mixed $value 值
* @throws \RuntimeException
*/
- private function addShortOption($shortcut, $value)
+ private function addShortOption(string $shortcut, $value): void
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
@@ -195,7 +196,7 @@ class Input
* @param mixed $value 值
* @throws \RuntimeException
*/
- private function addLongOption($name, $value)
+ private function addLongOption(string $name, $value): void
{
if (!$this->definition->hasOption($name)) {
throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name));
@@ -260,7 +261,7 @@ class Input
* @param string|array $values 需要检查的值
* @return bool
*/
- public function hasParameterOption($values)
+ public function hasParameterOption($values): bool
{
$values = (array) $values;
@@ -318,7 +319,7 @@ class Input
* 检查输入是否是交互的
* @return bool
*/
- public function isInteractive()
+ public function isInteractive(): bool
{
return $this->interactive;
}
@@ -327,16 +328,16 @@ class Input
* 设置输入的交互
* @param bool
*/
- public function setInteractive($interactive)
+ public function setInteractive(bool $interactive): void
{
- $this->interactive = (bool) $interactive;
+ $this->interactive = $interactive;
}
/**
* 获取所有的参数
* @return Argument[]
*/
- public function getArguments()
+ public function getArguments(): array
{
return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
}
@@ -347,13 +348,13 @@ class Input
* @return mixed
* @throws \InvalidArgumentException
*/
- public function getArgument($name)
+ public function getArgument(string $name)
{
if (!$this->definition->hasArgument($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
- return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)
+ return $this->arguments[$name] ?? $this->definition->getArgument($name)
->getDefault();
}
@@ -363,7 +364,7 @@ class Input
* @param string $value 值
* @throws \InvalidArgumentException
*/
- public function setArgument($name, $value)
+ public function setArgument(string $name, $value)
{
if (!$this->definition->hasArgument($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
@@ -377,7 +378,7 @@ class Input
* @param string|int $name 参数名或位置
* @return bool
*/
- public function hasArgument($name)
+ public function hasArgument($name): bool
{
return $this->definition->hasArgument($name);
}
@@ -386,7 +387,7 @@ class Input
* 获取所有的选项
* @return Option[]
*/
- public function getOptions()
+ public function getOptions(): array
{
return array_merge($this->definition->getOptionDefaults(), $this->options);
}
@@ -397,13 +398,13 @@ class Input
* @return mixed
* @throws \InvalidArgumentException
*/
- public function getOption($name)
+ public function getOption(string $name)
{
if (!$this->definition->hasOption($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
- return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
+ return $this->options[$name] ?? $this->definition->getOption($name)->getDefault();
}
/**
@@ -412,7 +413,7 @@ class Input
* @param string|bool $value 值
* @throws \InvalidArgumentException
*/
- public function setOption($name, $value)
+ public function setOption(string $name, $value): void
{
if (!$this->definition->hasOption($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
@@ -426,7 +427,7 @@ class Input
* @param string $name 选项名
* @return bool
*/
- public function hasOption($name)
+ public function hasOption(string $name): bool
{
return $this->definition->hasOption($name) && isset($this->options[$name]);
}
@@ -436,7 +437,7 @@ class Input
* @param string $token
* @return string
*/
- public function escapeToken($token)
+ public function escapeToken(string $token): string
{
return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
}
diff --git a/thinkphp/library/think/console/LICENSE b/vendor/topthink/framework/src/think/console/LICENSE
old mode 100755
new mode 100644
similarity index 100%
rename from thinkphp/library/think/console/LICENSE
rename to vendor/topthink/framework/src/think/console/LICENSE
diff --git a/thinkphp/library/think/console/Output.php b/vendor/topthink/framework/src/think/console/Output.php
old mode 100755
new mode 100644
similarity index 82%
rename from thinkphp/library/think/console/Output.php
rename to vendor/topthink/framework/src/think/console/Output.php
index 65dc9fb81..294c4b809
--- a/thinkphp/library/think/console/Output.php
+++ b/vendor/topthink/framework/src/think/console/Output.php
@@ -2,12 +2,13 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2020 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\console;
@@ -20,6 +21,7 @@ use think\console\output\driver\Nothing;
use think\console\output\Question;
use think\console\output\question\Choice;
use think\console\output\question\Confirmation;
+use Throwable;
/**
* Class Output
@@ -40,16 +42,22 @@ use think\console\output\question\Confirmation;
*/
class Output
{
+ // 不显示信息(静默)
const VERBOSITY_QUIET = 0;
+ // 正常信息
const VERBOSITY_NORMAL = 1;
+ // 详细信息
const VERBOSITY_VERBOSE = 2;
+ // 非常详细的信息
const VERBOSITY_VERY_VERBOSE = 3;
+ // 调试信息
const VERBOSITY_DEBUG = 4;
const OUTPUT_NORMAL = 0;
const OUTPUT_RAW = 1;
const OUTPUT_PLAIN = 2;
+ // 输出信息级别
private $verbosity = self::VERBOSITY_NORMAL;
/** @var Buffer|Console|Nothing */
@@ -61,7 +69,7 @@ class Output
'comment',
'question',
'highlight',
- 'warning'
+ 'warning',
];
public function __construct($driver = 'console')
@@ -119,7 +127,7 @@ class Output
return $answer;
}
- protected function block($style, $message)
+ protected function block(string $style, string $message): void
{
$this->writeln("<{$style}>{$message}$style>");
}
@@ -128,7 +136,7 @@ class Output
* 输出空行
* @param int $count
*/
- public function newLine($count = 1)
+ public function newLine(int $count = 1): void
{
$this->write(str_repeat(PHP_EOL, $count));
}
@@ -138,7 +146,7 @@ class Output
* @param string $messages
* @param int $type
*/
- public function writeln($messages, $type = self::OUTPUT_NORMAL)
+ public function writeln(string $messages, int $type = 0): void
{
$this->write($messages, true, $type);
}
@@ -149,53 +157,55 @@ class Output
* @param bool $newline
* @param int $type
*/
- public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
+ public function write(string $messages, bool $newline = false, int $type = 0): void
{
$this->handle->write($messages, $newline, $type);
}
- public function renderException(\Exception $e)
+ public function renderException(Throwable $e): void
{
$this->handle->renderException($e);
}
/**
- * {@inheritdoc}
+ * 设置输出信息级别
+ * @param int $level 输出信息级别
*/
- public function setVerbosity($level)
+ public function setVerbosity(int $level)
{
- $this->verbosity = (int) $level;
+ $this->verbosity = $level;
}
/**
- * {@inheritdoc}
+ * 获取输出信息级别
+ * @return int
*/
- public function getVerbosity()
+ public function getVerbosity(): int
{
return $this->verbosity;
}
- public function isQuiet()
+ public function isQuiet(): bool
{
return self::VERBOSITY_QUIET === $this->verbosity;
}
- public function isVerbose()
+ public function isVerbose(): bool
{
return self::VERBOSITY_VERBOSE <= $this->verbosity;
}
- public function isVeryVerbose()
+ public function isVeryVerbose(): bool
{
return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
}
- public function isDebug()
+ public function isDebug(): bool
{
return self::VERBOSITY_DEBUG <= $this->verbosity;
}
- public function describe($object, array $options = [])
+ public function describe($object, array $options = []): void
{
$descriptor = new Descriptor();
$options = array_merge([
@@ -218,5 +228,4 @@ class Output
throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
}
}
-
}
diff --git a/thinkphp/library/think/console/Table.php b/vendor/topthink/framework/src/think/console/Table.php
old mode 100755
new mode 100644
similarity index 79%
rename from thinkphp/library/think/console/Table.php
rename to vendor/topthink/framework/src/think/console/Table.php
index 9e28e266b..5a861d7fe
--- a/thinkphp/library/think/console/Table.php
+++ b/vendor/topthink/framework/src/think/console/Table.php
@@ -8,6 +8,7 @@
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\console;
@@ -108,7 +109,7 @@ class Table
* @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
* @return void
*/
- public function setHeader(array $header, $align = self::ALIGN_LEFT)
+ public function setHeader(array $header, int $align = 1): void
{
$this->header = $header;
$this->headerAlign = $align;
@@ -123,7 +124,7 @@ class Table
* @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
* @return void
*/
- public function setRows(array $rows, $align = self::ALIGN_LEFT)
+ public function setRows(array $rows, int $align = 1): void
{
$this->rows = $rows;
$this->cellAlign = $align;
@@ -133,18 +134,30 @@ class Table
}
}
+ /**
+ * 设置全局单元格对齐方式
+ * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
+ * @return $this
+ */
+ public function setCellAlign(int $align = 1)
+ {
+ $this->cellAlign = $align;
+ return $this;
+ }
+
/**
* 检查列数据的显示宽度
* @access public
* @param mixed $row 行数据
* @return void
*/
- protected function checkColWidth($row)
+ protected function checkColWidth($row): void
{
if (is_array($row)) {
foreach ($row as $key => $cell) {
- if (!isset($this->colWidth[$key]) || strlen($cell) > $this->colWidth[$key]) {
- $this->colWidth[$key] = strlen($cell);
+ $width = mb_strwidth((string) $cell);
+ if (!isset($this->colWidth[$key]) || $width > $this->colWidth[$key]) {
+ $this->colWidth[$key] = $width;
}
}
}
@@ -157,7 +170,7 @@ class Table
* @param bool $first 是否在开头插入
* @return void
*/
- public function addRow($row, $first = false)
+ public function addRow($row, bool $first = false): void
{
if ($first) {
array_unshift($this->rows, $row);
@@ -174,7 +187,7 @@ class Table
* @param string $style 样式名
* @return void
*/
- public function setStyle($style)
+ public function setStyle(string $style): void
{
$this->style = isset($this->format[$style]) ? $style : 'default';
}
@@ -185,7 +198,7 @@ class Table
* @param string $pos 位置
* @return string
*/
- protected function renderSeparator($pos)
+ protected function renderSeparator(string $pos): string
{
$style = $this->getStyle($pos);
$array = [];
@@ -202,7 +215,7 @@ class Table
* @access public
* @return string
*/
- protected function renderHeader()
+ protected function renderHeader(): string
{
$style = $this->getStyle('cell');
$content = $this->renderSeparator('top');
@@ -214,7 +227,7 @@ class Table
if (!empty($array)) {
$content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL;
- if ($this->rows) {
+ if (!empty($this->rows)) {
$content .= $this->renderSeparator('middle');
}
}
@@ -222,7 +235,7 @@ class Table
return $content;
}
- protected function getStyle($style)
+ protected function getStyle(string $style): array
{
if ($this->format[$this->style]) {
$style = $this->format[$this->style][$style];
@@ -239,9 +252,9 @@ class Table
* @param array $dataList 表格数据
* @return string
*/
- public function render($dataList = [])
+ public function render(array $dataList = []): string
{
- if ($dataList) {
+ if (!empty($dataList)) {
$this->setRows($dataList);
}
@@ -249,15 +262,16 @@ class Table
$content = $this->renderHeader();
$style = $this->getStyle('cell');
- if ($this->rows) {
+ if (!empty($this->rows)) {
foreach ($this->rows as $row) {
if (is_string($row) && '-' === $row) {
$content .= $this->renderSeparator('middle');
} elseif (is_scalar($row)) {
$content .= $this->renderSeparator('cross-top');
- $array = str_pad($row, 3 * (count($this->colWidth) - 1) + array_reduce($this->colWidth, function ($a, $b) {
+ $width = 3 * (count($this->colWidth) - 1) + array_reduce($this->colWidth, function ($a, $b) {
return $a + $b;
- }));
+ });
+ $array = str_pad($row, $width);
$content .= $style[0] . ' ' . $array . ' ' . $style[3] . PHP_EOL;
$content .= $this->renderSeparator('cross-bottom');
@@ -265,11 +279,16 @@ class Table
$array = [];
foreach ($row as $key => $val) {
- $array[] = ' ' . str_pad($val, $this->colWidth[$key], ' ', $this->cellAlign);
+ $width = $this->colWidth[$key];
+ // form https://github.com/symfony/console/blob/20c9821c8d1c2189f287dcee709b2f86353ea08f/Helper/Table.php#L467
+ // str_pad won't work properly with multi-byte strings, we need to fix the padding
+ if (false !== $encoding = mb_detect_encoding((string) $val, null, true)) {
+ $width += strlen((string) $val) - mb_strwidth((string) $val, $encoding);
+ }
+ $array[] = ' ' . str_pad((string) $val, $width, ' ', $this->cellAlign);
}
$content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL;
-
}
}
}
diff --git a/thinkphp/library/think/console/bin/README.md b/vendor/topthink/framework/src/think/console/bin/README.md
old mode 100755
new mode 100644
similarity index 100%
rename from thinkphp/library/think/console/bin/README.md
rename to vendor/topthink/framework/src/think/console/bin/README.md
diff --git a/thinkphp/library/think/console/bin/hiddeninput.exe b/vendor/topthink/framework/src/think/console/bin/hiddeninput.exe
old mode 100755
new mode 100644
similarity index 100%
rename from thinkphp/library/think/console/bin/hiddeninput.exe
rename to vendor/topthink/framework/src/think/console/bin/hiddeninput.exe
diff --git a/vendor/topthink/framework/src/think/console/command/Clear.php b/vendor/topthink/framework/src/think/console/command/Clear.php
new file mode 100644
index 000000000..da70b35d6
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/Clear.php
@@ -0,0 +1,85 @@
+
+// +----------------------------------------------------------------------
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Option;
+use think\console\Output;
+
+class Clear extends Command
+{
+ protected function configure()
+ {
+ // 指令配置
+ $this->setName('clear')
+ ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null)
+ ->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file')
+ ->addOption('log', 'l', Option::VALUE_NONE, 'clear log file')
+ ->addOption('dir', 'r', Option::VALUE_NONE, 'clear empty dir')
+ ->addOption('expire', 'e', Option::VALUE_NONE, 'clear cache file if cache has expired')
+ ->setDescription('Clear runtime file');
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $runtimePath = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR;
+
+ if ($input->getOption('cache')) {
+ $path = $runtimePath . 'cache';
+ } elseif ($input->getOption('log')) {
+ $path = $runtimePath . 'log';
+ } else {
+ $path = $input->getOption('path') ?: $runtimePath;
+ }
+
+ $rmdir = $input->getOption('dir') ? true : false;
+ // --expire 仅当 --cache 时生效
+ $cache_expire = $input->getOption('expire') && $input->getOption('cache') ? true : false;
+ $this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir, $cache_expire);
+
+ $output->writeln("Clear Successed ");
+ }
+
+ protected function clear(string $path, bool $rmdir, bool $cache_expire): void
+ {
+ $files = is_dir($path) ? scandir($path) : [];
+
+ foreach ($files as $file) {
+ if ('.' != $file && '..' != $file && is_dir($path . $file)) {
+ $this->clear($path . $file . DIRECTORY_SEPARATOR, $rmdir, $cache_expire);
+ if ($rmdir) {
+ @rmdir($path . $file);
+ }
+ } elseif ('.gitignore' != $file && is_file($path . $file)) {
+ if ($cache_expire) {
+ if ($this->cacheHasExpired($path . $file)) {
+ unlink($path . $file);
+ }
+ } else {
+ unlink($path . $file);
+ }
+ }
+ }
+ }
+
+ /**
+ * 缓存文件是否已过期
+ * @param $filename string 文件路径
+ * @return bool
+ */
+ protected function cacheHasExpired($filename) {
+ $content = file_get_contents($filename);
+ $expire = (int) substr($content, 8, 12);
+ return 0 != $expire && time() - $expire > filemtime($filename);
+ }
+
+}
diff --git a/thinkphp/library/think/console/command/Help.php b/vendor/topthink/framework/src/think/console/command/Help.php
old mode 100755
new mode 100644
similarity index 96%
rename from thinkphp/library/think/console/command/Help.php
rename to vendor/topthink/framework/src/think/console/command/Help.php
index f1b63b42e..2e4f2ca76
--- a/thinkphp/library/think/console/command/Help.php
+++ b/vendor/topthink/framework/src/think/console/command/Help.php
@@ -19,6 +19,7 @@ use think\console\Output;
class Help extends Command
{
+
private $command;
/**
@@ -31,7 +32,8 @@ class Help extends Command
$this->setName('help')->setDefinition([
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
- ])->setDescription('Displays help for a command')->setHelp(<<setDescription('Displays help for a command')->setHelp(
+ <<%command.name% command displays help for a given command:
php %command.full_name% list
@@ -45,7 +47,7 @@ EOF
* Sets the command.
* @param Command $command The command to set
*/
- public function setCommand(Command $command)
+ public function setCommand(Command $command): void
{
$this->command = $command;
}
diff --git a/thinkphp/library/think/console/command/Lists.php b/vendor/topthink/framework/src/think/console/command/Lists.php
old mode 100755
new mode 100644
similarity index 91%
rename from thinkphp/library/think/console/command/Lists.php
rename to vendor/topthink/framework/src/think/console/command/Lists.php
index 6eb856c26..d20fc751b
--- a/thinkphp/library/think/console/command/Lists.php
+++ b/vendor/topthink/framework/src/think/console/command/Lists.php
@@ -25,7 +25,8 @@ class Lists extends Command
*/
protected function configure()
{
- $this->setName('list')->setDefinition($this->createDefinition())->setDescription('Lists commands')->setHelp(<<setName('list')->setDefinition($this->createDefinition())->setDescription('Lists commands')->setHelp(
+ <<%command.name% command lists all commands:
php %command.full_name%
@@ -44,7 +45,7 @@ EOF
/**
* {@inheritdoc}
*/
- public function getNativeDefinition()
+ public function getNativeDefinition(): InputDefinition
{
return $this->createDefinition();
}
@@ -63,7 +64,7 @@ EOF
/**
* {@inheritdoc}
*/
- private function createDefinition()
+ private function createDefinition(): InputDefinition
{
return new InputDefinition([
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
diff --git a/thinkphp/library/think/console/command/Make.php b/vendor/topthink/framework/src/think/console/command/Make.php
old mode 100755
new mode 100644
similarity index 63%
rename from thinkphp/library/think/console/command/Make.php
rename to vendor/topthink/framework/src/think/console/command/Make.php
index 2f20954ac..662b33721
--- a/thinkphp/library/think/console/command/Make.php
+++ b/vendor/topthink/framework/src/think/console/command/Make.php
@@ -15,9 +15,6 @@ use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\Output;
-use think\facade\App;
-use think\facade\Config;
-use think\facade\Env;
abstract class Make extends Command
{
@@ -32,7 +29,6 @@ abstract class Make extends Command
protected function execute(Input $input, Output $output)
{
-
$name = trim($input->getArgument('name'));
$classname = $this->getClassName($name);
@@ -40,7 +36,7 @@ abstract class Make extends Command
$pathname = $this->getPathName($classname);
if (is_file($pathname)) {
- $output->writeln('' . $this->type . ' already exists! ');
+ $output->writeln('' . $this->type . ':' . $classname . ' already exists! ');
return false;
}
@@ -50,11 +46,10 @@ abstract class Make extends Command
file_put_contents($pathname, $this->buildClass($classname));
- $output->writeln('' . $this->type . ' created successfully. ');
-
+ $output->writeln('' . $this->type . ':' . $classname . ' created successfully. ');
}
- protected function buildClass($name)
+ protected function buildClass(string $name)
{
$stub = file_get_contents($this->getStub());
@@ -64,47 +59,41 @@ abstract class Make extends Command
return str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], [
$class,
- Config::get('action_suffix'),
+ $this->app->config->get('route.action_suffix'),
$namespace,
- App::getNamespace(),
+ $this->app->getNamespace(),
], $stub);
}
- protected function getPathName($name)
+ protected function getPathName(string $name): string
{
- $name = str_replace(App::getNamespace() . '\\', '', $name);
+ $name = str_replace('app\\', '', $name);
- return Env::get('app_path') . ltrim(str_replace('\\', '/', $name), '/') . '.php';
+ return $this->app->getBasePath() . ltrim(str_replace('\\', '/', $name), '/') . '.php';
}
- protected function getClassName($name)
+ protected function getClassName(string $name): string
{
- $appNamespace = App::getNamespace();
-
- if (strpos($name, $appNamespace . '\\') !== false) {
+ if (strpos($name, '\\') !== false) {
return $name;
}
- if (Config::get('app_multi_module')) {
- if (strpos($name, '/')) {
- list($module, $name) = explode('/', $name, 2);
- } else {
- $module = 'common';
- }
+ if (strpos($name, '@')) {
+ [$app, $name] = explode('@', $name);
} else {
- $module = null;
+ $app = '';
}
if (strpos($name, '/') !== false) {
$name = str_replace('/', '\\', $name);
}
- return $this->getNamespace($appNamespace, $module) . '\\' . $name;
+ return $this->getNamespace($app) . '\\' . $name;
}
- protected function getNamespace($appNamespace, $module)
+ protected function getNamespace(string $app): string
{
- return $module ? ($appNamespace . '\\' . $module) : $appNamespace;
+ return 'app' . ($app ? '\\' . $app : '');
}
}
diff --git a/thinkphp/library/think/console/command/RouteList.php b/vendor/topthink/framework/src/think/console/command/RouteList.php
old mode 100755
new mode 100644
similarity index 60%
rename from thinkphp/library/think/console/command/RouteList.php
rename to vendor/topthink/framework/src/think/console/command/RouteList.php
index 0405c31b6..ed579b856
--- a/thinkphp/library/think/console/command/RouteList.php
+++ b/vendor/topthink/framework/src/think/console/command/RouteList.php
@@ -16,7 +16,7 @@ use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\console\Table;
-use think\Container;
+use think\event\RouteLoaded;
class RouteList extends Command
{
@@ -31,6 +31,7 @@ class RouteList extends Command
protected function configure()
{
$this->setName('route:list')
+ ->addArgument('dir', Argument::OPTIONAL, 'dir name .')
->addArgument('style', Argument::OPTIONAL, "the style of the table.", 'default')
->addOption('sort', 's', Option::VALUE_OPTIONAL, 'order by rule name.', 0)
->addOption('more', 'm', Option::VALUE_NONE, 'show route options.')
@@ -39,79 +40,77 @@ class RouteList extends Command
protected function execute(Input $input, Output $output)
{
- $filename = Container::get('app')->getRuntimePath() . 'route_list.php';
+ $dir = $input->getArgument('dir') ?: '';
+
+ $filename = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : '') . 'route_list.php';
if (is_file($filename)) {
unlink($filename);
+ } elseif (!is_dir(dirname($filename))) {
+ mkdir(dirname($filename), 0755);
}
- $content = $this->getRouteList();
+ $content = $this->getRouteList($dir);
file_put_contents($filename, 'Route List' . PHP_EOL . $content);
}
- protected function getRouteList()
+ protected function getRouteList(string $dir = null): string
{
- Container::get('route')->setTestMode(true);
- // 路由检测
- $path = Container::get('app')->getRoutePath();
+ $this->app->route->setTestMode(true);
+ $this->app->route->clear();
+
+ if ($dir) {
+ $path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR;
+ } else {
+ $path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR;
+ }
$files = is_dir($path) ? scandir($path) : [];
foreach ($files as $file) {
if (strpos($file, '.php')) {
- $filename = $path . DIRECTORY_SEPARATOR . $file;
- // 导入路由配置
- $rules = include $filename;
-
- if (is_array($rules)) {
- Container::get('route')->import($rules);
- }
+ include $path . $file;
}
}
- if (Container::get('config')->get('route_annotation')) {
- $suffix = Container::get('config')->get('controller_suffix') || Container::get('config')->get('class_suffix');
-
- include Container::get('build')->buildRoute($suffix);
- }
+ //触发路由载入完成事件
+ $this->app->event->trigger(RouteLoaded::class);
$table = new Table();
if ($this->input->hasOption('more')) {
$header = ['Rule', 'Route', 'Method', 'Name', 'Domain', 'Option', 'Pattern'];
} else {
- $header = ['Rule', 'Route', 'Method', 'Name', 'Domain'];
+ $header = ['Rule', 'Route', 'Method', 'Name'];
}
$table->setHeader($header);
- $routeList = Container::get('route')->getRuleList();
+ $routeList = $this->app->route->getRuleList();
$rows = [];
- foreach ($routeList as $domain => $items) {
- foreach ($items as $item) {
- $item['route'] = $item['route'] instanceof \Closure ? '' : $item['route'];
+ foreach ($routeList as $item) {
+ $item['route'] = $item['route'] instanceof \Closure ? '' : $item['route'];
- if ($this->input->hasOption('more')) {
- $item = [$item['rule'], $item['route'], $item['method'], $item['name'], $domain, json_encode($item['option']), json_encode($item['pattern'])];
- } else {
- $item = [$item['rule'], $item['route'], $item['method'], $item['name'], $domain];
- }
-
- $rows[] = $item;
+ if ($this->input->hasOption('more')) {
+ $item = [$item['rule'], $item['route'], $item['method'], $item['name'], $item['domain'], json_encode($item['option']), json_encode($item['pattern'])];
+ } else {
+ $item = [$item['rule'], $item['route'], $item['method'], $item['name']];
}
+
+ $rows[] = $item;
}
if ($this->input->getOption('sort')) {
- $sort = $this->input->getOption('sort');
+ $sort = strtolower($this->input->getOption('sort'));
if (isset($this->sortBy[$sort])) {
$sort = $this->sortBy[$sort];
}
uasort($rows, function ($a, $b) use ($sort) {
- $itemA = isset($a[$sort]) ? $a[$sort] : null;
- $itemB = isset($b[$sort]) ? $b[$sort] : null;
+ $itemA = $a[$sort] ?? null;
+ $itemB = $b[$sort] ?? null;
return strcasecmp($itemA, $itemB);
});
diff --git a/thinkphp/library/think/console/command/RunServer.php b/vendor/topthink/framework/src/think/console/command/RunServer.php
old mode 100755
new mode 100644
similarity index 65%
rename from thinkphp/library/think/console/command/RunServer.php
rename to vendor/topthink/framework/src/think/console/command/RunServer.php
index 2e028dc6d..20a246627
--- a/thinkphp/library/think/console/command/RunServer.php
+++ b/vendor/topthink/framework/src/think/console/command/RunServer.php
@@ -8,25 +8,41 @@
// +----------------------------------------------------------------------
// | Author: Slince
// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
-use think\facade\App;
class RunServer extends Command
{
public function configure()
{
$this->setName('run')
- ->addOption('host', 'H', Option::VALUE_OPTIONAL,
- 'The host to server the application on', '127.0.0.1')
- ->addOption('port', 'p', Option::VALUE_OPTIONAL,
- 'The port to server the application on', 8000)
- ->addOption('root', 'r', Option::VALUE_OPTIONAL,
- 'The document root of the application', App::getRootPath() . 'public')
+ ->addOption(
+ 'host',
+ 'H',
+ Option::VALUE_OPTIONAL,
+ 'The host to server the application on',
+ '0.0.0.0'
+ )
+ ->addOption(
+ 'port',
+ 'p',
+ Option::VALUE_OPTIONAL,
+ 'The port to server the application on',
+ 8000
+ )
+ ->addOption(
+ 'root',
+ 'r',
+ Option::VALUE_OPTIONAL,
+ 'The document root of the application',
+ ''
+ )
->setDescription('PHP Built-in Server for ThinkPHP');
}
@@ -35,6 +51,9 @@ class RunServer extends Command
$host = $input->getOption('host');
$port = $input->getOption('port');
$root = $input->getOption('root');
+ if (empty($root)) {
+ $root = $this->app->getRootPath() . 'public';
+ }
$command = sprintf(
'php -S %s:%d -t %s %s',
@@ -44,7 +63,7 @@ class RunServer extends Command
escapeshellarg($root . DIRECTORY_SEPARATOR . 'router.php')
);
- $output->writeln(sprintf('ThinkPHP Development server is started On ', $host, $port));
+ $output->writeln(sprintf('ThinkPHP Development server is started On ', '0.0.0.0' == $host ? '127.0.0.1' : $host, $port));
$output->writeln(sprintf('You can exit with `CTRL-C` '));
$output->writeln(sprintf('Document root is: %s', $root));
passthru($command);
diff --git a/vendor/topthink/framework/src/think/console/command/ServiceDiscover.php b/vendor/topthink/framework/src/think/console/command/ServiceDiscover.php
new file mode 100644
index 000000000..e90f43398
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/ServiceDiscover.php
@@ -0,0 +1,52 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\Output;
+
+class ServiceDiscover extends Command
+{
+ public function configure()
+ {
+ $this->setName('service:discover')
+ ->setDescription('Discover Services for ThinkPHP');
+ }
+
+ public function execute(Input $input, Output $output)
+ {
+ if (is_file($path = $this->app->getRootPath() . 'vendor/composer/installed.json')) {
+ $packages = json_decode(@file_get_contents($path), true);
+ // Compatibility with Composer 2.0
+ if (isset($packages['packages'])) {
+ $packages = $packages['packages'];
+ }
+
+ $services = [];
+ foreach ($packages as $package) {
+ if (!empty($package['extra']['think']['services'])) {
+ $services = array_merge($services, (array) $package['extra']['think']['services']);
+ }
+ }
+
+ $header = '// This file is automatically generated at:' . date('Y-m-d H:i:s') . PHP_EOL . 'declare (strict_types = 1);' . PHP_EOL;
+
+ $content = 'app->getRootPath() . 'vendor/services.php', $content);
+
+ $output->writeln('Succeed! ');
+ }
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/VendorPublish.php b/vendor/topthink/framework/src/think/console/command/VendorPublish.php
new file mode 100644
index 000000000..399876575
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/VendorPublish.php
@@ -0,0 +1,69 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\input\Option;
+
+class VendorPublish extends Command
+{
+ public function configure()
+ {
+ $this->setName('vendor:publish')
+ ->addOption('force', 'f', Option::VALUE_NONE, 'Overwrite any existing files')
+ ->setDescription('Publish any publishable assets from vendor packages');
+ }
+
+ public function handle()
+ {
+
+ $force = $this->input->getOption('force');
+
+ if (is_file($path = $this->app->getRootPath() . 'vendor/composer/installed.json')) {
+ $packages = json_decode(@file_get_contents($path), true);
+ // Compatibility with Composer 2.0
+ if (isset($packages['packages'])) {
+ $packages = $packages['packages'];
+ }
+ foreach ($packages as $package) {
+ //配置
+ $configDir = $this->app->getConfigPath();
+
+ if (!empty($package['extra']['think']['config'])) {
+
+ $installPath = $this->app->getRootPath() . 'vendor/' . $package['name'] . DIRECTORY_SEPARATOR;
+
+ foreach ((array) $package['extra']['think']['config'] as $name => $file) {
+
+ $target = $configDir . $name . '.php';
+ $source = $installPath . $file;
+
+ if (is_file($target) && !$force) {
+ $this->output->info("File {$target} exist!");
+ continue;
+ }
+
+ if (!is_file($source)) {
+ $this->output->info("File {$source} not exist!");
+ continue;
+ }
+
+ copy($source, $target);
+ }
+ }
+ }
+
+ $this->output->writeln('Succeed! ');
+ }
+ }
+}
diff --git a/thinkphp/library/think/console/command/Version.php b/vendor/topthink/framework/src/think/console/command/Version.php
old mode 100755
new mode 100644
similarity index 92%
rename from thinkphp/library/think/console/command/Version.php
rename to vendor/topthink/framework/src/think/console/command/Version.php
index ee7eca9c9..beb49d2c6
--- a/thinkphp/library/think/console/command/Version.php
+++ b/vendor/topthink/framework/src/think/console/command/Version.php
@@ -8,12 +8,13 @@
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
-use think\facade\App;
class Version extends Command
{
@@ -26,6 +27,7 @@ class Version extends Command
protected function execute(Input $input, Output $output)
{
- $output->writeln('v' . App::version());
+ $output->writeln('v' . $this->app->version());
}
+
}
diff --git a/thinkphp/library/think/console/command/make/Command.php b/vendor/topthink/framework/src/think/console/command/make/Command.php
old mode 100755
new mode 100644
similarity index 81%
rename from thinkphp/library/think/console/command/make/Command.php
rename to vendor/topthink/framework/src/think/console/command/make/Command.php
index b539eb236..9549a0215
--- a/thinkphp/library/think/console/command/make/Command.php
+++ b/vendor/topthink/framework/src/think/console/command/make/Command.php
@@ -2,18 +2,17 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
-// | Author: 刘志淳
+// | Author: liu21st
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
use think\console\input\Argument;
-use think\facade\App;
class Command extends Make
{
@@ -27,7 +26,7 @@ class Command extends Make
->setDescription('Create a new command class');
}
- protected function buildClass($name)
+ protected function buildClass(string $name): string
{
$commandName = $this->input->getArgument('commandName') ?: strtolower(basename($name));
$namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
@@ -39,18 +38,18 @@ class Command extends Make
$commandName,
$class,
$namespace,
- App::getNamespace(),
+ $this->app->getNamespace(),
], $stub);
}
- protected function getStub()
+ protected function getStub(): string
{
return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'command.stub';
}
- protected function getNamespace($appNamespace, $module)
+ protected function getNamespace(string $app): string
{
- return $appNamespace . '\\command';
+ return parent::getNamespace($app) . '\\command';
}
}
diff --git a/thinkphp/library/think/console/command/make/Controller.php b/vendor/topthink/framework/src/think/console/command/make/Controller.php
old mode 100755
new mode 100644
similarity index 75%
rename from thinkphp/library/think/console/command/make/Controller.php
rename to vendor/topthink/framework/src/think/console/command/make/Controller.php
index 2a6ab770d..4a8d226c0
--- a/thinkphp/library/think/console/command/make/Controller.php
+++ b/vendor/topthink/framework/src/think/console/command/make/Controller.php
@@ -2,21 +2,21 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
-// | Author: 刘志淳
+// | Author: liu21st
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
use think\console\input\Option;
-use think\facade\Config;
class Controller extends Make
{
+
protected $type = "Controller";
protected function configure()
@@ -28,7 +28,7 @@ class Controller extends Make
->setDescription('Create a new resource controller class');
}
- protected function getStub()
+ protected function getStub(): string
{
$stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR;
@@ -43,14 +43,14 @@ class Controller extends Make
return $stubPath . 'controller.stub';
}
- protected function getClassName($name)
+ protected function getClassName(string $name): string
{
- return parent::getClassName($name) . (Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : '');
+ return parent::getClassName($name) . ($this->app->config->get('route.controller_suffix') ? 'Controller' : '');
}
- protected function getNamespace($appNamespace, $module)
+ protected function getNamespace(string $app): string
{
- return parent::getNamespace($appNamespace, $module) . '\controller';
+ return parent::getNamespace($app) . '\\controller';
}
}
diff --git a/vendor/topthink/framework/src/think/console/command/make/Event.php b/vendor/topthink/framework/src/think/console/command/make/Event.php
new file mode 100644
index 000000000..6b1668984
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/Event.php
@@ -0,0 +1,35 @@
+
+// +----------------------------------------------------------------------
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Event extends Make
+{
+ protected $type = "Event";
+
+ protected function configure()
+ {
+ parent::configure();
+ $this->setName('make:event')
+ ->setDescription('Create a new event class');
+ }
+
+ protected function getStub(): string
+ {
+ return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'event.stub';
+ }
+
+ protected function getNamespace(string $app): string
+ {
+ return parent::getNamespace($app) . '\\event';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/Listener.php b/vendor/topthink/framework/src/think/console/command/make/Listener.php
new file mode 100644
index 000000000..5c9267368
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/Listener.php
@@ -0,0 +1,35 @@
+
+// +----------------------------------------------------------------------
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Listener extends Make
+{
+ protected $type = "Listener";
+
+ protected function configure()
+ {
+ parent::configure();
+ $this->setName('make:listener')
+ ->setDescription('Create a new listener class');
+ }
+
+ protected function getStub(): string
+ {
+ return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'listener.stub';
+ }
+
+ protected function getNamespace(string $app): string
+ {
+ return parent::getNamespace($app) . '\\listener';
+ }
+}
diff --git a/thinkphp/library/think/console/command/make/Middleware.php b/vendor/topthink/framework/src/think/console/command/make/Middleware.php
old mode 100755
new mode 100644
similarity index 73%
rename from thinkphp/library/think/console/command/make/Middleware.php
rename to vendor/topthink/framework/src/think/console/command/make/Middleware.php
index bfe821b03..3b68b4a7f
--- a/thinkphp/library/think/console/command/make/Middleware.php
+++ b/vendor/topthink/framework/src/think/console/command/make/Middleware.php
@@ -1,12 +1,12 @@
+// | Author: liu21st
// +----------------------------------------------------------------------
namespace think\console\command\make;
@@ -24,13 +24,13 @@ class Middleware extends Make
->setDescription('Create a new middleware class');
}
- protected function getStub()
+ protected function getStub(): string
{
return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'middleware.stub';
}
- protected function getNamespace($appNamespace, $module)
+ protected function getNamespace(string $app): string
{
- return parent::getNamespace($appNamespace, 'http') . '\middleware';
+ return parent::getNamespace($app) . '\\middleware';
}
}
diff --git a/thinkphp/library/think/console/command/make/Model.php b/vendor/topthink/framework/src/think/console/command/make/Model.php
old mode 100755
new mode 100644
similarity index 73%
rename from thinkphp/library/think/console/command/make/Model.php
rename to vendor/topthink/framework/src/think/console/command/make/Model.php
index 03e6b3fcd..cb7a23c4f
--- a/thinkphp/library/think/console/command/make/Model.php
+++ b/vendor/topthink/framework/src/think/console/command/make/Model.php
@@ -1,12 +1,12 @@
+// | Author: liu21st
// +----------------------------------------------------------------------
namespace think\console\command\make;
@@ -24,13 +24,13 @@ class Model extends Make
->setDescription('Create a new model class');
}
- protected function getStub()
+ protected function getStub(): string
{
return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub';
}
- protected function getNamespace($appNamespace, $module)
+ protected function getNamespace(string $app): string
{
- return parent::getNamespace($appNamespace, $module) . '\model';
+ return parent::getNamespace($app) . '\\model';
}
}
diff --git a/vendor/topthink/framework/src/think/console/command/make/Service.php b/vendor/topthink/framework/src/think/console/command/make/Service.php
new file mode 100644
index 000000000..c4bbaa0eb
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/Service.php
@@ -0,0 +1,36 @@
+
+// +----------------------------------------------------------------------
+
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Service extends Make
+{
+ protected $type = "Service";
+
+ protected function configure()
+ {
+ parent::configure();
+ $this->setName('make:service')
+ ->setDescription('Create a new Service class');
+ }
+
+ protected function getStub(): string
+ {
+ return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'service.stub';
+ }
+
+ protected function getNamespace(string $app): string
+ {
+ return parent::getNamespace($app) . '\\service';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/Subscribe.php b/vendor/topthink/framework/src/think/console/command/make/Subscribe.php
new file mode 100644
index 000000000..a1dc2a820
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/Subscribe.php
@@ -0,0 +1,35 @@
+
+// +----------------------------------------------------------------------
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Subscribe extends Make
+{
+ protected $type = "Subscribe";
+
+ protected function configure()
+ {
+ parent::configure();
+ $this->setName('make:subscribe')
+ ->setDescription('Create a new subscribe class');
+ }
+
+ protected function getStub(): string
+ {
+ return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'subscribe.stub';
+ }
+
+ protected function getNamespace(string $app): string
+ {
+ return parent::getNamespace($app) . '\\subscribe';
+ }
+}
diff --git a/thinkphp/library/think/console/command/make/Validate.php b/vendor/topthink/framework/src/think/console/command/make/Validate.php
old mode 100755
new mode 100644
similarity index 77%
rename from thinkphp/library/think/console/command/make/Validate.php
rename to vendor/topthink/framework/src/think/console/command/make/Validate.php
index 89830ad1d..8d3643161
--- a/thinkphp/library/think/console/command/make/Validate.php
+++ b/vendor/topthink/framework/src/think/console/command/make/Validate.php
@@ -2,11 +2,11 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
-// | Author: 刘志淳
+// | Author: liu21st
// +----------------------------------------------------------------------
namespace think\console\command\make;
@@ -24,16 +24,16 @@ class Validate extends Make
->setDescription('Create a validate class');
}
- protected function getStub()
+ protected function getStub(): string
{
$stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR;
return $stubPath . 'validate.stub';
}
- protected function getNamespace($appNamespace, $module)
+ protected function getNamespace(string $app): string
{
- return parent::getNamespace($appNamespace, $module) . '\validate';
+ return parent::getNamespace($app) . '\\validate';
}
}
diff --git a/thinkphp/library/think/console/command/make/stubs/command.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/command.stub
old mode 100755
new mode 100644
similarity index 52%
rename from thinkphp/library/think/console/command/make/stubs/command.stub
rename to vendor/topthink/framework/src/think/console/command/make/stubs/command.stub
index d2c7c1e7e..3ee2b1cf9
--- a/thinkphp/library/think/console/command/make/stubs/command.stub
+++ b/vendor/topthink/framework/src/think/console/command/make/stubs/command.stub
@@ -1,9 +1,12 @@
setName('{%commandName%}');
- // 设置参数
-
+ $this->setName('{%commandName%}')
+ ->setDescription('the {%commandName%} command');
}
protected function execute(Input $input, Output $output)
{
- // 指令输出
- $output->writeln('{%commandName%}');
+ // 指令输出
+ $output->writeln('{%commandName%}');
}
}
diff --git a/thinkphp/library/think/console/command/make/stubs/controller.api.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub
old mode 100755
new mode 100644
similarity index 94%
rename from thinkphp/library/think/console/command/make/stubs/controller.api.stub
rename to vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub
index 54ec0594e..5d3383d20
--- a/thinkphp/library/think/console/command/make/stubs/controller.api.stub
+++ b/vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub
@@ -1,11 +1,11 @@
['规则1','规则2'...]
+ * 格式:'字段名' => ['规则1','规则2'...]
*
* @var array
- */
- protected $rule = [];
-
+ */
+ protected $rule = [];
+
/**
* 定义错误信息
- * 格式:'字段名.规则名' => '错误信息'
+ * 格式:'字段名.规则名' => '错误信息'
*
* @var array
- */
+ */
protected $message = [];
}
diff --git a/thinkphp/library/think/console/command/optimize/Route.php b/vendor/topthink/framework/src/think/console/command/optimize/Route.php
old mode 100755
new mode 100644
similarity index 53%
rename from thinkphp/library/think/console/command/optimize/Route.php
rename to vendor/topthink/framework/src/think/console/command/optimize/Route.php
index f6dc63288..56f7f5a9b
--- a/thinkphp/library/think/console/command/optimize/Route.php
+++ b/vendor/topthink/framework/src/think/console/command/optimize/Route.php
@@ -12,55 +12,55 @@ namespace think\console\command\optimize;
use think\console\Command;
use think\console\Input;
+use think\console\input\Argument;
use think\console\Output;
-use think\Container;
+use think\event\RouteLoaded;
class Route extends Command
{
protected function configure()
{
$this->setName('optimize:route')
- ->setDescription('Build route cache.');
+ ->addArgument('dir', Argument::OPTIONAL, 'dir name .')
+ ->setDescription('Build app route cache.');
}
protected function execute(Input $input, Output $output)
{
- $filename = Container::get('app')->getRuntimePath() . 'route.php';
+ $dir = $input->getArgument('dir') ?: '';
+
+ $path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : '');
+
+ $filename = $path . 'route.php';
if (is_file($filename)) {
unlink($filename);
}
- file_put_contents($filename, $this->buildRouteCache());
+
+ file_put_contents($filename, $this->buildRouteCache($dir));
$output->writeln('Succeed! ');
}
- protected function buildRouteCache()
+ protected function buildRouteCache(string $dir = null): string
{
- Container::get('route')->setName([]);
- Container::get('route')->setTestMode(true);
+ $this->app->route->clear();
+ $this->app->route->lazy(false);
+
// 路由检测
- $path = Container::get('app')->getRoutePath();
+ $path = $this->app->getRootPath() . ($dir ? 'app' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR : '') . 'route' . DIRECTORY_SEPARATOR;
$files = is_dir($path) ? scandir($path) : [];
foreach ($files as $file) {
if (strpos($file, '.php')) {
- $filename = $path . DIRECTORY_SEPARATOR . $file;
- // 导入路由配置
- $rules = include $filename;
- if (is_array($rules)) {
- Container::get('route')->import($rules);
- }
+ include $path . $file;
}
}
- if (Container::get('config')->get('route_annotation')) {
- $suffix = Container::get('config')->get('controller_suffix') || Container::get('config')->get('class_suffix');
- include Container::get('build')->buildRoute($suffix);
- }
+ //触发路由载入完成事件
+ $this->app->event->trigger(RouteLoaded::class);
+ $rules = $this->app->route->getName();
- $content = 'getName(), true) . ';';
- return $content;
+ return '
+// +----------------------------------------------------------------------
+namespace think\console\command\optimize;
+
+use Exception;
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\input\Option;
+use think\console\Output;
+use think\db\PDOConnection;
+
+class Schema extends Command
+{
+ protected function configure()
+ {
+ $this->setName('optimize:schema')
+ ->addArgument('dir', Argument::OPTIONAL, 'dir name .')
+ ->addOption('connection', null, Option::VALUE_REQUIRED, 'connection name .')
+ ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .')
+ ->setDescription('Build database schema cache.');
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $dir = $input->getArgument('dir') ?: '';
+
+ if ($input->hasOption('table')) {
+ $connection = $this->app->db->connect($input->getOption('connection'));
+ if (!$connection instanceof PDOConnection) {
+ $output->error("only PDO connection support schema cache!");
+ return;
+ }
+ $table = $input->getOption('table');
+ if (false === strpos($table, '.')) {
+ $dbName = $connection->getConfig('database');
+ } else {
+ [$dbName, $table] = explode('.', $table);
+ }
+
+ if ($table == '*') {
+ $table = $connection->getTables($dbName);
+ }
+
+ $this->buildDataBaseSchema($connection, (array) $table, $dbName);
+ } else {
+ if ($dir) {
+ $appPath = $this->app->getBasePath() . $dir . DIRECTORY_SEPARATOR;
+ $namespace = 'app\\' . $dir;
+ } else {
+ $appPath = $this->app->getBasePath();
+ $namespace = 'app';
+ }
+
+ $path = $appPath . 'model';
+ $list = is_dir($path) ? scandir($path) : [];
+
+ foreach ($list as $file) {
+ if (0 === strpos($file, '.')) {
+ continue;
+ }
+ $class = '\\' . $namespace . '\\model\\' . pathinfo($file, PATHINFO_FILENAME);
+ $this->buildModelSchema($class);
+ }
+ }
+
+ $output->writeln('Succeed! ');
+ }
+
+ protected function buildModelSchema(string $class): void
+ {
+ $reflect = new \ReflectionClass($class);
+ if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) {
+ try {
+ /** @var \think\Model $model */
+ $model = new $class;
+ $connection = $model->db()->getConnection();
+ if ($connection instanceof PDOConnection) {
+ $table = $model->getTable();
+ //预读字段信息
+ $connection->getSchemaInfo($table, true);
+ }
+ } catch (Exception $e) {
+
+ }
+ }
+ }
+
+ protected function buildDataBaseSchema(PDOConnection $connection, array $tables, string $dbName): void
+ {
+ foreach ($tables as $table) {
+ //预读字段信息
+ $connection->getSchemaInfo("{$dbName}.{$table}", true);
+ }
+ }
+}
diff --git a/thinkphp/library/think/console/input/Argument.php b/vendor/topthink/framework/src/think/console/input/Argument.php
old mode 100755
new mode 100644
similarity index 82%
rename from thinkphp/library/think/console/input/Argument.php
rename to vendor/topthink/framework/src/think/console/input/Argument.php
index 16223bbeb..86cca36cb
--- a/thinkphp/library/think/console/input/Argument.php
+++ b/vendor/topthink/framework/src/think/console/input/Argument.php
@@ -13,14 +13,37 @@ namespace think\console\input;
class Argument
{
-
+ // 必传参数
const REQUIRED = 1;
+
+ // 可选参数
const OPTIONAL = 2;
+
+ // 数组参数
const IS_ARRAY = 4;
+ /**
+ * 参数名
+ * @var string
+ */
private $name;
+
+ /**
+ * 参数类型
+ * @var int
+ */
private $mode;
+
+ /**
+ * 参数默认值
+ * @var mixed
+ */
private $default;
+
+ /**
+ * 参数描述
+ * @var string
+ */
private $description;
/**
@@ -31,7 +54,7 @@ class Argument
* @param mixed $default 默认值 (仅 self::OPTIONAL 类型有效)
* @throws \InvalidArgumentException
*/
- public function __construct($name, $mode = null, $description = '', $default = null)
+ public function __construct(string $name, int $mode = null, string $description = '', $default = null)
{
if (null === $mode) {
$mode = self::OPTIONAL;
@@ -50,7 +73,7 @@ class Argument
* 获取参数名
* @return string
*/
- public function getName()
+ public function getName(): string
{
return $this->name;
}
@@ -59,7 +82,7 @@ class Argument
* 是否必须
* @return bool
*/
- public function isRequired()
+ public function isRequired(): bool
{
return self::REQUIRED === (self::REQUIRED & $this->mode);
}
@@ -68,7 +91,7 @@ class Argument
* 该参数是否接受数组
* @return bool
*/
- public function isArray()
+ public function isArray(): bool
{
return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
}
@@ -78,7 +101,7 @@ class Argument
* @param mixed $default 默认值
* @throws \LogicException
*/
- public function setDefault($default = null)
+ public function setDefault($default = null): void
{
if (self::REQUIRED === $this->mode && null !== $default) {
throw new \LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
@@ -108,7 +131,7 @@ class Argument
* 获取描述
* @return string
*/
- public function getDescription()
+ public function getDescription(): string
{
return $this->description;
}
diff --git a/thinkphp/library/think/console/input/Definition.php b/vendor/topthink/framework/src/think/console/input/Definition.php
old mode 100755
new mode 100644
similarity index 88%
rename from thinkphp/library/think/console/input/Definition.php
rename to vendor/topthink/framework/src/think/console/input/Definition.php
index c71977ec3..ccf02a0c7
--- a/thinkphp/library/think/console/input/Definition.php
+++ b/vendor/topthink/framework/src/think/console/input/Definition.php
@@ -43,7 +43,7 @@ class Definition
* 设置指令的定义
* @param array $definition 定义的数组
*/
- public function setDefinition(array $definition)
+ public function setDefinition(array $definition): void
{
$arguments = [];
$options = [];
@@ -63,7 +63,7 @@ class Definition
* 设置参数
* @param Argument[] $arguments 参数数组
*/
- public function setArguments($arguments = [])
+ public function setArguments(array $arguments = []): void
{
$this->arguments = [];
$this->requiredCount = 0;
@@ -77,7 +77,7 @@ class Definition
* @param Argument[] $arguments 参数数组
* @api
*/
- public function addArguments($arguments = [])
+ public function addArguments(array $arguments = []): void
{
if (null !== $arguments) {
foreach ($arguments as $argument) {
@@ -91,7 +91,7 @@ class Definition
* @param Argument $argument 参数
* @throws \LogicException
*/
- public function addArgument(Argument $argument)
+ public function addArgument(Argument $argument): void
{
if (isset($this->arguments[$argument->getName()])) {
throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName()));
@@ -124,7 +124,7 @@ class Definition
* @return Argument 参数
* @throws \InvalidArgumentException
*/
- public function getArgument($name)
+ public function getArgument($name): Argument
{
if (!$this->hasArgument($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
@@ -141,7 +141,7 @@ class Definition
* @return bool
* @api
*/
- public function hasArgument($name)
+ public function hasArgument($name): bool
{
$arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
@@ -152,7 +152,7 @@ class Definition
* 获取所有的参数
* @return Argument[] 参数数组
*/
- public function getArguments()
+ public function getArguments(): array
{
return $this->arguments;
}
@@ -161,7 +161,7 @@ class Definition
* 获取参数数量
* @return int
*/
- public function getArgumentCount()
+ public function getArgumentCount(): int
{
return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments);
}
@@ -170,7 +170,7 @@ class Definition
* 获取必填的参数的数量
* @return int
*/
- public function getArgumentRequiredCount()
+ public function getArgumentRequiredCount(): int
{
return $this->requiredCount;
}
@@ -179,7 +179,7 @@ class Definition
* 获取参数默认值
* @return array
*/
- public function getArgumentDefaults()
+ public function getArgumentDefaults(): array
{
$values = [];
foreach ($this->arguments as $argument) {
@@ -193,7 +193,7 @@ class Definition
* 设置选项
* @param Option[] $options 选项数组
*/
- public function setOptions($options = [])
+ public function setOptions(array $options = []): void
{
$this->options = [];
$this->shortcuts = [];
@@ -205,7 +205,7 @@ class Definition
* @param Option[] $options 选项数组
* @api
*/
- public function addOptions($options = [])
+ public function addOptions(array $options = []): void
{
foreach ($options as $option) {
$this->addOption($option);
@@ -218,7 +218,7 @@ class Definition
* @throws \LogicException
* @api
*/
- public function addOption(Option $option)
+ public function addOption(Option $option): void
{
if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) {
throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName()));
@@ -249,7 +249,7 @@ class Definition
* @throws \InvalidArgumentException
* @api
*/
- public function getOption($name)
+ public function getOption(string $name): Option
{
if (!$this->hasOption($name)) {
throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
@@ -264,7 +264,7 @@ class Definition
* @return bool
* @api
*/
- public function hasOption($name)
+ public function hasOption(string $name): bool
{
return isset($this->options[$name]);
}
@@ -274,7 +274,7 @@ class Definition
* @return Option[]
* @api
*/
- public function getOptions()
+ public function getOptions(): array
{
return $this->options;
}
@@ -284,7 +284,7 @@ class Definition
* @param string $name 短名称
* @return bool
*/
- public function hasShortcut($name)
+ public function hasShortcut(string $name): bool
{
return isset($this->shortcuts[$name]);
}
@@ -294,7 +294,7 @@ class Definition
* @param string $shortcut 短名称
* @return Option
*/
- public function getOptionForShortcut($shortcut)
+ public function getOptionForShortcut(string $shortcut): Option
{
return $this->getOption($this->shortcutToName($shortcut));
}
@@ -303,7 +303,7 @@ class Definition
* 获取所有选项的默认值
* @return array
*/
- public function getOptionDefaults()
+ public function getOptionDefaults(): array
{
$values = [];
foreach ($this->options as $option) {
@@ -319,7 +319,7 @@ class Definition
* @return string
* @throws \InvalidArgumentException
*/
- private function shortcutToName($shortcut)
+ private function shortcutToName(string $shortcut): string
{
if (!isset($this->shortcuts[$shortcut])) {
throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
@@ -333,7 +333,7 @@ class Definition
* @param bool $short 是否简洁介绍
* @return string
*/
- public function getSynopsis($short = false)
+ public function getSynopsis(bool $short = false): string
{
$elements = [];
diff --git a/thinkphp/library/think/console/input/Option.php b/vendor/topthink/framework/src/think/console/input/Option.php
old mode 100755
new mode 100644
similarity index 93%
rename from thinkphp/library/think/console/input/Option.php
rename to vendor/topthink/framework/src/think/console/input/Option.php
index e5707c9ae..19c7e1e86
--- a/thinkphp/library/think/console/input/Option.php
+++ b/vendor/topthink/framework/src/think/console/input/Option.php
@@ -11,18 +11,49 @@
namespace think\console\input;
+/**
+ * 命令行选项
+ * @package think\console\input
+ */
class Option
{
-
+ // 无需传值
const VALUE_NONE = 1;
+ // 必须传值
const VALUE_REQUIRED = 2;
+ // 可选传值
const VALUE_OPTIONAL = 4;
+ // 传数组值
const VALUE_IS_ARRAY = 8;
+ /**
+ * 选项名
+ * @var string
+ */
private $name;
+
+ /**
+ * 选项短名称
+ * @var string
+ */
private $shortcut;
+
+ /**
+ * 选项类型
+ * @var int
+ */
private $mode;
+
+ /**
+ * 选项默认值
+ * @var mixed
+ */
private $default;
+
+ /**
+ * 选项描述
+ * @var string
+ */
private $description;
/**
diff --git a/thinkphp/library/think/console/output/Ask.php b/vendor/topthink/framework/src/think/console/output/Ask.php
old mode 100755
new mode 100644
similarity index 99%
rename from thinkphp/library/think/console/output/Ask.php
rename to vendor/topthink/framework/src/think/console/output/Ask.php
index 3933eb296..56821c72d
--- a/thinkphp/library/think/console/output/Ask.php
+++ b/vendor/topthink/framework/src/think/console/output/Ask.php
@@ -197,10 +197,6 @@ class Ask
$value = rtrim(shell_exec($exe));
$this->output->writeln('');
- if (isset($tmpExe)) {
- unlink($tmpExe);
- }
-
return $value;
}
diff --git a/thinkphp/library/think/console/output/Descriptor.php b/vendor/topthink/framework/src/think/console/output/Descriptor.php
old mode 100755
new mode 100644
similarity index 94%
rename from thinkphp/library/think/console/output/Descriptor.php
rename to vendor/topthink/framework/src/think/console/output/Descriptor.php
index 6d98d53c7..e4a9e61b8
--- a/thinkphp/library/think/console/output/Descriptor.php
+++ b/vendor/topthink/framework/src/think/console/output/Descriptor.php
@@ -82,7 +82,7 @@ class Descriptor
$default = '';
}
- $totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName());
+ $totalWidth = $options['total_width'] ?? strlen($argument->getName());
$spacingWidth = $totalWidth - strlen($argument->getName()) + 2;
$this->writeText(sprintf(" %s %s%s%s", $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces
@@ -115,7 +115,7 @@ class Descriptor
}
}
- $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]);
+ $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]);
$synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', sprintf('--%s%s', $option->getName(), $value));
$spacingWidth = $totalWidth - strlen($synopsis) + 2;
@@ -216,7 +216,7 @@ class Descriptor
$description = new ConsoleDescription($console, $describedNamespace);
if (isset($options['raw_text']) && $options['raw_text']) {
- $width = $this->getColumnWidth($description->getCommands());
+ $width = $this->getColumnWidth($description->getNamespaces());
foreach ($description->getCommands() as $command) {
$this->writeText(sprintf("%-${width}s %s", $command->getName(), $command->getDescription()), $options);
@@ -235,7 +235,7 @@ class Descriptor
$this->writeText("\n");
$this->writeText("\n");
- $width = $this->getColumnWidth($description->getCommands());
+ $width = $this->getColumnWidth($description->getNamespaces());
if ($describedNamespace) {
$this->writeText(sprintf('Available commands for the "%s" namespace: ', $describedNamespace), $options);
@@ -282,14 +282,18 @@ class Descriptor
}
/**
- * @param Command[] $commands
+ * @param Namespaces[] $namespaces
* @return int
*/
- private function getColumnWidth(array $commands)
+ private function getColumnWidth(array $namespaces)
{
$width = 0;
- foreach ($commands as $command) {
- $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width;
+ foreach ($namespaces as $namespace) {
+ foreach ($namespace['commands'] as $name) {
+ if (strlen($name) > $width) {
+ $width = strlen($name);
+ }
+ }
}
return $width + 2;
diff --git a/thinkphp/library/think/console/output/Formatter.php b/vendor/topthink/framework/src/think/console/output/Formatter.php
old mode 100755
new mode 100644
similarity index 98%
rename from thinkphp/library/think/console/output/Formatter.php
rename to vendor/topthink/framework/src/think/console/output/Formatter.php
index f8bee5527..1b97ca324
--- a/thinkphp/library/think/console/output/Formatter.php
+++ b/vendor/topthink/framework/src/think/console/output/Formatter.php
@@ -123,7 +123,7 @@ class Formatter
if ($open = '/' != $text[1]) {
$tag = $matches[1][$i][0];
} else {
- $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : '';
+ $tag = $matches[3][$i][0] ?? '';
}
if (!$open && !$tag) {
diff --git a/thinkphp/library/think/console/output/Question.php b/vendor/topthink/framework/src/think/console/output/Question.php
old mode 100755
new mode 100644
similarity index 100%
rename from thinkphp/library/think/console/output/Question.php
rename to vendor/topthink/framework/src/think/console/output/Question.php
diff --git a/thinkphp/library/think/console/output/descriptor/Console.php b/vendor/topthink/framework/src/think/console/output/descriptor/Console.php
old mode 100755
new mode 100644
similarity index 92%
rename from thinkphp/library/think/console/output/descriptor/Console.php
rename to vendor/topthink/framework/src/think/console/output/descriptor/Console.php
index 8739c536e..ff9f4641e
--- a/thinkphp/library/think/console/output/descriptor/Console.php
+++ b/vendor/topthink/framework/src/think/console/output/descriptor/Console.php
@@ -58,7 +58,7 @@ class Console
/**
* @return array
*/
- public function getNamespaces()
+ public function getNamespaces(): array
{
if (null === $this->namespaces) {
$this->inspectConsole();
@@ -70,7 +70,7 @@ class Console
/**
* @return Command[]
*/
- public function getCommands()
+ public function getCommands(): array
{
if (null === $this->commands) {
$this->inspectConsole();
@@ -84,16 +84,16 @@ class Console
* @return Command
* @throws \InvalidArgumentException
*/
- public function getCommand($name)
+ public function getCommand(string $name): Command
{
if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name));
}
- return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name];
+ return $this->commands[$name] ?? $this->aliases[$name];
}
- private function inspectConsole()
+ private function inspectConsole(): void
{
$this->commands = [];
$this->namespaces = [];
@@ -129,7 +129,7 @@ class Console
* @param array $commands
* @return array
*/
- private function sortCommands(array $commands)
+ private function sortCommands(array $commands): array
{
$namespacedCommands = [];
foreach ($commands as $name => $command) {
diff --git a/thinkphp/library/think/console/output/driver/Buffer.php b/vendor/topthink/framework/src/think/console/output/driver/Buffer.php
old mode 100755
new mode 100644
similarity index 89%
rename from thinkphp/library/think/console/output/driver/Buffer.php
rename to vendor/topthink/framework/src/think/console/output/driver/Buffer.php
index c77a2ec4b..576f31ac3
--- a/thinkphp/library/think/console/output/driver/Buffer.php
+++ b/vendor/topthink/framework/src/think/console/output/driver/Buffer.php
@@ -32,7 +32,7 @@ class Buffer
return $content;
}
- public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL)
+ public function write($messages, bool $newline = false, int $options = 0)
{
$messages = (array) $messages;
@@ -44,7 +44,7 @@ class Buffer
}
}
- public function renderException(\Exception $e)
+ public function renderException(\Throwable $e)
{
// do nothing
}
diff --git a/thinkphp/library/think/console/output/driver/Console.php b/vendor/topthink/framework/src/think/console/output/driver/Console.php
old mode 100755
new mode 100644
similarity index 95%
rename from thinkphp/library/think/console/output/driver/Console.php
rename to vendor/topthink/framework/src/think/console/output/driver/Console.php
index e041b5250..31bdf1f52
--- a/thinkphp/library/think/console/output/driver/Console.php
+++ b/vendor/topthink/framework/src/think/console/output/driver/Console.php
@@ -42,7 +42,7 @@ class Console
$this->formatter->setDecorated($decorated);
}
- public function write($messages, $newline = false, $type = Output::OUTPUT_NORMAL, $stream = null)
+ public function write($messages, bool $newline = false, int $type = 0, $stream = null)
{
if (Output::VERBOSITY_QUIET === $this->output->getVerbosity()) {
return;
@@ -68,7 +68,7 @@ class Console
}
}
- public function renderException(\Exception $e)
+ public function renderException(\Throwable $e)
{
$stderr = $this->openErrorStream();
$decorated = $this->hasColorSupport($stderr);
@@ -162,7 +162,7 @@ class Console
* 获取当前终端的尺寸
* @return array
*/
- public function getTerminalDimensions()
+ public function getTerminalDimensions(): array
{
if ($this->terminalDimensions) {
return $this->terminalDimensions;
@@ -237,7 +237,7 @@ class Console
return;
}
- private function stringWidth($string)
+ private function stringWidth(string $string): int
{
if (!function_exists('mb_strwidth')) {
return strlen($string);
@@ -250,7 +250,7 @@ class Console
return mb_strwidth($string, $encoding);
}
- private function splitStringByWidth($string, $width)
+ private function splitStringByWidth(string $string, int $width): array
{
if (!function_exists('mb_strwidth')) {
return str_split($string, $width);
@@ -280,7 +280,7 @@ class Console
return $lines;
}
- private function isRunningOS400()
+ private function isRunningOS400(): bool
{
$checks = [
function_exists('php_uname') ? php_uname('s') : '',
@@ -295,7 +295,7 @@ class Console
*
* @return bool
*/
- protected function hasStdoutSupport()
+ protected function hasStdoutSupport(): bool
{
return false === $this->isRunningOS400();
}
@@ -305,7 +305,7 @@ class Console
*
* @return bool
*/
- protected function hasStderrSupport()
+ protected function hasStderrSupport(): bool
{
return false === $this->isRunningOS400();
}
@@ -352,7 +352,7 @@ class Console
* @param $stream
* @return bool
*/
- protected function hasColorSupport($stream)
+ protected function hasColorSupport($stream): bool
{
if (DIRECTORY_SEPARATOR === '\\') {
return
diff --git a/thinkphp/library/think/console/output/driver/Nothing.php b/vendor/topthink/framework/src/think/console/output/driver/Nothing.php
old mode 100755
new mode 100644
similarity index 85%
rename from thinkphp/library/think/console/output/driver/Nothing.php
rename to vendor/topthink/framework/src/think/console/output/driver/Nothing.php
index 9a55f7779..a7cc49e24
--- a/thinkphp/library/think/console/output/driver/Nothing.php
+++ b/vendor/topthink/framework/src/think/console/output/driver/Nothing.php
@@ -21,12 +21,12 @@ class Nothing
// do nothing
}
- public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL)
+ public function write($messages, bool $newline = false, int $options = 0)
{
// do nothing
}
- public function renderException(\Exception $e)
+ public function renderException(\Throwable $e)
{
// do nothing
}
diff --git a/thinkphp/library/think/console/output/formatter/Stack.php b/vendor/topthink/framework/src/think/console/output/formatter/Stack.php
old mode 100755
new mode 100644
similarity index 92%
rename from thinkphp/library/think/console/output/formatter/Stack.php
rename to vendor/topthink/framework/src/think/console/output/formatter/Stack.php
index 4864a3f29..536625991
--- a/thinkphp/library/think/console/output/formatter/Stack.php
+++ b/vendor/topthink/framework/src/think/console/output/formatter/Stack.php
@@ -37,7 +37,7 @@ class Stack
/**
* 重置堆栈
*/
- public function reset()
+ public function reset(): void
{
$this->styles = [];
}
@@ -46,7 +46,7 @@ class Stack
* 推一个样式进入堆栈
* @param Style $style
*/
- public function push(Style $style)
+ public function push(Style $style): void
{
$this->styles[] = $style;
}
@@ -57,7 +57,7 @@ class Stack
* @return Style
* @throws \InvalidArgumentException
*/
- public function pop(Style $style = null)
+ public function pop(Style $style = null): Style
{
if (empty($this->styles)) {
return $this->emptyStyle;
@@ -86,7 +86,7 @@ class Stack
* 计算堆栈的当前样式。
* @return Style
*/
- public function getCurrent()
+ public function getCurrent(): Style
{
if (empty($this->styles)) {
return $this->emptyStyle;
@@ -109,7 +109,7 @@ class Stack
/**
* @return Style
*/
- public function getEmptyStyle()
+ public function getEmptyStyle(): Style
{
return $this->emptyStyle;
}
diff --git a/thinkphp/library/think/console/output/formatter/Style.php b/vendor/topthink/framework/src/think/console/output/formatter/Style.php
old mode 100755
new mode 100644
similarity index 95%
rename from thinkphp/library/think/console/output/formatter/Style.php
rename to vendor/topthink/framework/src/think/console/output/formatter/Style.php
index d9b099987..2aae76829
--- a/thinkphp/library/think/console/output/formatter/Style.php
+++ b/vendor/topthink/framework/src/think/console/output/formatter/Style.php
@@ -13,8 +13,7 @@ namespace think\console\output\formatter;
class Style
{
-
- private static $availableForegroundColors = [
+ protected static $availableForegroundColors = [
'black' => ['set' => 30, 'unset' => 39],
'red' => ['set' => 31, 'unset' => 39],
'green' => ['set' => 32, 'unset' => 39],
@@ -24,7 +23,8 @@ class Style
'cyan' => ['set' => 36, 'unset' => 39],
'white' => ['set' => 37, 'unset' => 39],
];
- private static $availableBackgroundColors = [
+
+ protected static $availableBackgroundColors = [
'black' => ['set' => 40, 'unset' => 49],
'red' => ['set' => 41, 'unset' => 49],
'green' => ['set' => 42, 'unset' => 49],
@@ -34,7 +34,8 @@ class Style
'cyan' => ['set' => 46, 'unset' => 49],
'white' => ['set' => 47, 'unset' => 49],
];
- private static $availableOptions = [
+
+ protected static $availableOptions = [
'bold' => ['set' => 1, 'unset' => 22],
'underscore' => ['set' => 4, 'unset' => 24],
'blink' => ['set' => 5, 'unset' => 25],
@@ -114,7 +115,7 @@ class Style
* @throws \InvalidArgumentException When the option name isn't defined
* @api
*/
- public function setOption($option)
+ public function setOption(string $option): void
{
if (!isset(static::$availableOptions[$option])) {
throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
@@ -130,7 +131,7 @@ class Style
* @param string $option 格式名
* @throws \InvalidArgumentException
*/
- public function unsetOption($option)
+ public function unsetOption(string $option): void
{
if (!isset(static::$availableOptions[$option])) {
throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
@@ -160,7 +161,7 @@ class Style
* @param string $text 文字
* @return string
*/
- public function apply($text)
+ public function apply(string $text): string
{
$setCodes = [];
$unsetCodes = [];
diff --git a/thinkphp/library/think/console/output/question/Choice.php b/vendor/topthink/framework/src/think/console/output/question/Choice.php
old mode 100755
new mode 100644
similarity index 94%
rename from thinkphp/library/think/console/output/question/Choice.php
rename to vendor/topthink/framework/src/think/console/output/question/Choice.php
index cdc3b4e4a..1da1750cc
--- a/thinkphp/library/think/console/output/question/Choice.php
+++ b/vendor/topthink/framework/src/think/console/output/question/Choice.php
@@ -40,7 +40,7 @@ class Choice extends Question
* 可选项
* @return array
*/
- public function getChoices()
+ public function getChoices(): array
{
return $this->choices;
}
@@ -50,7 +50,7 @@ class Choice extends Question
* @param bool $multiselect
* @return self
*/
- public function setMultiselect($multiselect)
+ public function setMultiselect(bool $multiselect)
{
$this->multiselect = $multiselect;
$this->setValidator($this->getDefaultValidator());
@@ -58,7 +58,7 @@ class Choice extends Question
return $this;
}
- public function isMultiselect()
+ public function isMultiselect(): bool
{
return $this->multiselect;
}
@@ -67,7 +67,7 @@ class Choice extends Question
* 获取提示
* @return string
*/
- public function getPrompt()
+ public function getPrompt(): string
{
return $this->prompt;
}
@@ -77,7 +77,7 @@ class Choice extends Question
* @param string $prompt
* @return self
*/
- public function setPrompt($prompt)
+ public function setPrompt(string $prompt)
{
$this->prompt = $prompt;
@@ -89,7 +89,7 @@ class Choice extends Question
* @param string $errorMessage
* @return self
*/
- public function setErrorMessage($errorMessage)
+ public function setErrorMessage(string $errorMessage)
{
$this->errorMessage = $errorMessage;
$this->setValidator($this->getDefaultValidator());
diff --git a/thinkphp/library/think/console/output/question/Confirmation.php b/vendor/topthink/framework/src/think/console/output/question/Confirmation.php
old mode 100755
new mode 100644
similarity index 94%
rename from thinkphp/library/think/console/output/question/Confirmation.php
rename to vendor/topthink/framework/src/think/console/output/question/Confirmation.php
index 6598f9b3e..bf71b5d6d
--- a/thinkphp/library/think/console/output/question/Confirmation.php
+++ b/vendor/topthink/framework/src/think/console/output/question/Confirmation.php
@@ -24,7 +24,7 @@ class Confirmation extends Question
* @param bool $default 默认答案
* @param string $trueAnswerRegex 验证正则
*/
- public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i')
+ public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i')
{
parent::__construct($question, (bool) $default);
diff --git a/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php b/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php
new file mode 100644
index 000000000..da5e69632
--- /dev/null
+++ b/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php
@@ -0,0 +1,88 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\contract;
+
+/**
+ * 缓存驱动接口
+ */
+interface CacheHandlerInterface
+{
+ /**
+ * 判断缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return bool
+ */
+ public function has($name);
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get($name, $default = null);
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer|\DateTime $expire 有效时间(秒)
+ * @return bool
+ */
+ public function set($name, $value, $expire = null);
+
+ /**
+ * 自增缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function inc(string $name, int $step = 1);
+
+ /**
+ * 自减缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function dec(string $name, int $step = 1);
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return bool
+ */
+ public function delete($name);
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return bool
+ */
+ public function clear();
+
+ /**
+ * 删除缓存标签
+ * @access public
+ * @param array $keys 缓存标识列表
+ * @return void
+ */
+ public function clearTag(array $keys);
+
+}
diff --git a/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php b/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php
new file mode 100644
index 000000000..896ac29db
--- /dev/null
+++ b/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php
@@ -0,0 +1,28 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\contract;
+
+/**
+ * 日志驱动接口
+ */
+interface LogHandlerInterface
+{
+ /**
+ * 日志写入接口
+ * @access public
+ * @param array $log 日志信息
+ * @return bool
+ */
+ public function save(array $log): bool;
+
+}
diff --git a/vendor/topthink/framework/src/think/contract/ModelRelationInterface.php b/vendor/topthink/framework/src/think/contract/ModelRelationInterface.php
new file mode 100644
index 000000000..1f6f994e8
--- /dev/null
+++ b/vendor/topthink/framework/src/think/contract/ModelRelationInterface.php
@@ -0,0 +1,99 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\contract;
+
+use Closure;
+use think\Collection;
+use think\db\Query;
+use think\Model;
+
+/**
+ * 模型关联接口
+ */
+interface ModelRelationInterface
+{
+ /**
+ * 延迟获取关联数据
+ * @access public
+ * @param array $subRelation 子关联
+ * @param Closure $closure 闭包查询条件
+ * @return Collection
+ */
+ public function getRelation(array $subRelation = [], Closure $closure = null): Collection;
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包条件
+ * @return void
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null): void;
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包条件
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null): void;
+
+ /**
+ * 关联统计
+ * @access public
+ * @param Model $result 模型对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return integer
+ */
+ public function relationCount(Model $result, Closure $closure, string $aggregate = 'count', string $field = '*', string &$name = null);
+
+ /**
+ * 创建关联统计子查询
+ * @access public
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return string
+ */
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string;
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @return Query
+ */
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER'): Query;
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @return Query
+ */
+ public function hasWhere($where = [], $fields = null, string $joinType = ''): Query;
+}
diff --git a/thinkphp/library/think/config/driver/Json.php b/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php
old mode 100755
new mode 100644
similarity index 57%
rename from thinkphp/library/think/config/driver/Json.php
rename to vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php
index 0d77c8edc..0b2e41424
--- a/thinkphp/library/think/config/driver/Json.php
+++ b/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php
@@ -2,30 +2,22 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
-namespace think\config\driver;
+namespace think\contract;
-class Json
+/**
+ * Session驱动接口
+ */
+interface SessionHandlerInterface
{
- protected $config;
-
- public function __construct($config)
- {
- if (is_file($config)) {
- $config = file_get_contents($config);
- }
-
- $this->config = $config;
- }
-
- public function parse()
- {
- return json_decode($this->config, true);
- }
+ public function read(string $sessionId): string;
+ public function delete(string $sessionId): bool;
+ public function write(string $sessionId, string $data): bool;
}
diff --git a/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php b/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php
new file mode 100644
index 000000000..9be93d2e4
--- /dev/null
+++ b/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php
@@ -0,0 +1,61 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\contract;
+
+/**
+ * 视图驱动接口
+ */
+interface TemplateHandlerInterface
+{
+ /**
+ * 检测是否存在模板文件
+ * @access public
+ * @param string $template 模板文件或者模板规则
+ * @return bool
+ */
+ public function exists(string $template): bool;
+
+ /**
+ * 渲染模板文件
+ * @access public
+ * @param string $template 模板文件
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function fetch(string $template, array $data = []): void;
+
+ /**
+ * 渲染模板内容
+ * @access public
+ * @param string $content 模板内容
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function display(string $content, array $data = []): void;
+
+ /**
+ * 配置模板引擎
+ * @access private
+ * @param array $config 参数
+ * @return void
+ */
+ public function config(array $config): void;
+
+ /**
+ * 获取模板引擎配置
+ * @access public
+ * @param string $name 参数名
+ * @return void
+ */
+ public function getConfig(string $name);
+}
diff --git a/thinkphp/library/think/route/dispatch/Response.php b/vendor/topthink/framework/src/think/event/AppInit.php
old mode 100755
new mode 100644
similarity index 69%
rename from thinkphp/library/think/route/dispatch/Response.php
rename to vendor/topthink/framework/src/think/event/AppInit.php
index 66f4e5abb..dda820b5e
--- a/thinkphp/library/think/route/dispatch/Response.php
+++ b/vendor/topthink/framework/src/think/event/AppInit.php
@@ -2,22 +2,18 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
-namespace think\route\dispatch;
+namespace think\event;
-use think\route\Dispatch;
-
-class Response extends Dispatch
-{
- public function exec()
- {
- return $this->dispatch;
- }
-
-}
+/**
+ * AppInit事件类
+ */
+class AppInit
+{}
diff --git a/thinkphp/library/think/route/dispatch/Redirect.php b/vendor/topthink/framework/src/think/event/HttpEnd.php
old mode 100755
new mode 100644
similarity index 64%
rename from thinkphp/library/think/route/dispatch/Redirect.php
rename to vendor/topthink/framework/src/think/event/HttpEnd.php
index fae2c9a65..c40da57d7
--- a/thinkphp/library/think/route/dispatch/Redirect.php
+++ b/vendor/topthink/framework/src/think/event/HttpEnd.php
@@ -2,22 +2,18 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
-namespace think\route\dispatch;
+namespace think\event;
-use think\Response;
-use think\route\Dispatch;
-
-class Redirect extends Dispatch
-{
- public function exec()
- {
- return Response::create($this->dispatch, 'redirect')->code($this->code);
- }
-}
+/**
+ * HttpEnd事件类
+ */
+class HttpEnd
+{}
diff --git a/vendor/topthink/framework/src/think/event/HttpRun.php b/vendor/topthink/framework/src/think/event/HttpRun.php
new file mode 100644
index 000000000..ce67e93e4
--- /dev/null
+++ b/vendor/topthink/framework/src/think/event/HttpRun.php
@@ -0,0 +1,19 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\event;
+
+/**
+ * HttpRun事件类
+ */
+class HttpRun
+{}
diff --git a/vendor/topthink/framework/src/think/event/LogRecord.php b/vendor/topthink/framework/src/think/event/LogRecord.php
new file mode 100644
index 000000000..237468dd5
--- /dev/null
+++ b/vendor/topthink/framework/src/think/event/LogRecord.php
@@ -0,0 +1,29 @@
+
+// +----------------------------------------------------------------------
+namespace think\event;
+
+/**
+ * LogRecord事件类
+ */
+class LogRecord
+{
+ /** @var string */
+ public $type;
+
+ /** @var string */
+ public $message;
+
+ public function __construct($type, $message)
+ {
+ $this->type = $type;
+ $this->message = $message;
+ }
+}
diff --git a/thinkphp/library/think/route/dispatch/View.php b/vendor/topthink/framework/src/think/event/LogWrite.php
old mode 100755
new mode 100644
similarity index 59%
rename from thinkphp/library/think/route/dispatch/View.php
rename to vendor/topthink/framework/src/think/event/LogWrite.php
index ea3ef11b0..a7873018d
--- a/thinkphp/library/think/route/dispatch/View.php
+++ b/vendor/topthink/framework/src/think/event/LogWrite.php
@@ -2,25 +2,30 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
-namespace think\route\dispatch;
+namespace think\event;
-use think\Response;
-use think\route\Dispatch;
-
-class View extends Dispatch
+/**
+ * LogWrite事件类
+ */
+class LogWrite
{
- public function exec()
- {
- // 渲染模板输出
- $vars = array_merge($this->request->param(), $this->param);
+ /** @var string */
+ public $channel;
- return Response::create($this->dispatch, 'view')->assign($vars);
+ /** @var array */
+ public $log;
+
+ public function __construct($channel, $log)
+ {
+ $this->channel = $channel;
+ $this->log = $log;
}
}
diff --git a/vendor/topthink/framework/src/think/event/RouteLoaded.php b/vendor/topthink/framework/src/think/event/RouteLoaded.php
new file mode 100644
index 000000000..ace7992f3
--- /dev/null
+++ b/vendor/topthink/framework/src/think/event/RouteLoaded.php
@@ -0,0 +1,21 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\event;
+
+/**
+ * 路由加载完成事件
+ */
+class RouteLoaded
+{
+
+}
diff --git a/thinkphp/library/think/exception/ClassNotFoundException.php b/vendor/topthink/framework/src/think/exception/ClassNotFoundException.php
old mode 100755
new mode 100644
similarity index 67%
rename from thinkphp/library/think/exception/ClassNotFoundException.php
rename to vendor/topthink/framework/src/think/exception/ClassNotFoundException.php
index eb22e7301..c4cda77d5
--- a/thinkphp/library/think/exception/ClassNotFoundException.php
+++ b/vendor/topthink/framework/src/think/exception/ClassNotFoundException.php
@@ -2,7 +2,7 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
@@ -11,13 +11,20 @@
namespace think\exception;
-class ClassNotFoundException extends \RuntimeException
+use Psr\Container\NotFoundExceptionInterface;
+use RuntimeException;
+use Throwable;
+
+class ClassNotFoundException extends RuntimeException implements NotFoundExceptionInterface
{
protected $class;
- public function __construct($message, $class = '')
+
+ public function __construct(string $message, string $class = '', Throwable $previous = null)
{
$this->message = $message;
$this->class = $class;
+
+ parent::__construct($message, 0, $previous);
}
/**
diff --git a/thinkphp/library/think/exception/ErrorException.php b/vendor/topthink/framework/src/think/exception/ErrorException.php
old mode 100755
new mode 100644
similarity index 89%
rename from thinkphp/library/think/exception/ErrorException.php
rename to vendor/topthink/framework/src/think/exception/ErrorException.php
index 3143b8f70..d1a237803
--- a/thinkphp/library/think/exception/ErrorException.php
+++ b/vendor/topthink/framework/src/think/exception/ErrorException.php
@@ -2,12 +2,13 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\exception;
@@ -35,7 +36,7 @@ class ErrorException extends Exception
* @param string $file 出错文件路径
* @param integer $line 出错行号
*/
- public function __construct($severity, $message, $file, $line)
+ public function __construct(int $severity, string $message, string $file, int $line)
{
$this->severity = $severity;
$this->message = $message;
diff --git a/vendor/topthink/framework/src/think/exception/FileException.php b/vendor/topthink/framework/src/think/exception/FileException.php
new file mode 100644
index 000000000..228a1898c
--- /dev/null
+++ b/vendor/topthink/framework/src/think/exception/FileException.php
@@ -0,0 +1,17 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\exception;
+
+class FileException extends \RuntimeException
+{
+}
diff --git a/vendor/topthink/framework/src/think/exception/FuncNotFoundException.php b/vendor/topthink/framework/src/think/exception/FuncNotFoundException.php
new file mode 100644
index 000000000..ee2bcad2e
--- /dev/null
+++ b/vendor/topthink/framework/src/think/exception/FuncNotFoundException.php
@@ -0,0 +1,30 @@
+message = $message;
+ $this->func = $func;
+
+ parent::__construct($message, 0, $previous);
+ }
+
+ /**
+ * 获取方法名
+ * @access public
+ * @return string
+ */
+ public function getFunc()
+ {
+ return $this->func;
+ }
+}
diff --git a/thinkphp/library/think/exception/Handle.php b/vendor/topthink/framework/src/think/exception/Handle.php
old mode 100755
new mode 100644
similarity index 54%
rename from thinkphp/library/think/exception/Handle.php
rename to vendor/topthink/framework/src/think/exception/Handle.php
index 02c85ec13..1f783bc52
--- a/thinkphp/library/think/exception/Handle.php
+++ b/vendor/topthink/framework/src/think/exception/Handle.php
@@ -2,44 +2,60 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\exception;
use Exception;
+use think\App;
use think\console\Output;
-use think\Container;
+use think\db\exception\DataNotFoundException;
+use think\db\exception\ModelNotFoundException;
+use think\Request;
use think\Response;
+use Throwable;
+/**
+ * 系统异常处理类
+ */
class Handle
{
- protected $render;
+ /** @var App */
+ protected $app;
+
protected $ignoreReport = [
- '\\think\\exception\\HttpException',
+ HttpException::class,
+ HttpResponseException::class,
+ ModelNotFoundException::class,
+ DataNotFoundException::class,
+ ValidateException::class,
];
- public function setRender($render)
+ protected $isJson = false;
+
+ public function __construct(App $app)
{
- $this->render = $render;
+ $this->app = $app;
}
/**
* Report or log an exception.
*
* @access public
- * @param \Exception $exception
+ * @param Throwable $exception
* @return void
*/
- public function report(Exception $exception)
+ public function report(Throwable $exception): void
{
if (!$this->isIgnoreReport($exception)) {
// 收集异常数据
- if (Container::get('app')->isDebug()) {
+ if ($this->app->isDebug()) {
$data = [
'file' => $exception->getFile(),
'line' => $exception->getLine(),
@@ -55,15 +71,17 @@ class Handle
$log = "[{$data['code']}]{$data['message']}";
}
- if (Container::get('app')->config('log.record_trace')) {
- $log .= "\r\n" . $exception->getTraceAsString();
+ if ($this->app->config->get('log.record_trace')) {
+ $log .= PHP_EOL . $exception->getTraceAsString();
}
- Container::get('log')->record($log, 'error');
+ try {
+ $this->app->log->record($log, 'error');
+ } catch (Exception $e) {}
}
}
- protected function isIgnoreReport(Exception $exception)
+ protected function isIgnoreReport(Throwable $exception): bool
{
foreach ($this->ignoreReport as $class) {
if ($exception instanceof $class) {
@@ -78,20 +96,16 @@ class Handle
* Render an exception into an HTTP response.
*
* @access public
- * @param \Exception $e
+ * @param Request $request
+ * @param Throwable $e
* @return Response
*/
- public function render(Exception $e)
+ public function render($request, Throwable $e): Response
{
- if ($this->render && $this->render instanceof \Closure) {
- $result = call_user_func_array($this->render, [$e]);
-
- if ($result) {
- return $result;
- }
- }
-
- if ($e instanceof HttpException) {
+ $this->isJson = $request->isJson();
+ if ($e instanceof HttpResponseException) {
+ return $e->getResponse();
+ } elseif ($e instanceof HttpException) {
return $this->renderHttpException($e);
} else {
return $this->convertExceptionToResponse($e);
@@ -100,12 +114,12 @@ class Handle
/**
* @access public
- * @param Output $output
- * @param Exception $e
+ * @param Output $output
+ * @param Throwable $e
*/
- public function renderForConsole(Output $output, Exception $e)
+ public function renderForConsole(Output $output, Throwable $e): void
{
- if (Container::get('app')->isDebug()) {
+ if ($this->app->isDebug()) {
$output->setVerbosity(Output::VERBOSITY_DEBUG);
}
@@ -114,15 +128,15 @@ class Handle
/**
* @access protected
- * @param HttpException $e
+ * @param HttpException $e
* @return Response
*/
- protected function renderHttpException(HttpException $e)
+ protected function renderHttpException(HttpException $e): Response
{
$status = $e->getStatusCode();
- $template = Container::get('app')->config('http_exception_template');
+ $template = $this->app->config->get('app.http_exception_template');
- if (!Container::get('app')->isDebug() && !empty($template[$status])) {
+ if (!$this->app->isDebug() && !empty($template[$status])) {
return Response::create($template[$status], 'view', $status)->assign(['e' => $e]);
} else {
return $this->convertExceptionToResponse($e);
@@ -130,33 +144,39 @@ class Handle
}
/**
- * @access protected
- * @param Exception $exception
- * @return Response
+ * 收集异常数据
+ * @param Throwable $exception
+ * @return array
*/
- protected function convertExceptionToResponse(Exception $exception)
+ protected function convertExceptionToArray(Throwable $exception): array
{
- // 收集异常数据
- if (Container::get('app')->isDebug()) {
+ if ($this->app->isDebug()) {
// 调试模式,获取详细的错误信息
+ $traces = [];
+ $nextException = $exception;
+ do {
+ $traces[] = [
+ 'name' => get_class($nextException),
+ 'file' => $nextException->getFile(),
+ 'line' => $nextException->getLine(),
+ 'code' => $this->getCode($nextException),
+ 'message' => $this->getMessage($nextException),
+ 'trace' => $nextException->getTrace(),
+ 'source' => $this->getSourceCode($nextException),
+ ];
+ } while ($nextException = $nextException->getPrevious());
$data = [
- 'name' => get_class($exception),
- 'file' => $exception->getFile(),
- 'line' => $exception->getLine(),
- 'message' => $this->getMessage($exception),
- 'trace' => $exception->getTrace(),
'code' => $this->getCode($exception),
- 'source' => $this->getSourceCode($exception),
+ 'message' => $this->getMessage($exception),
+ 'traces' => $traces,
'datas' => $this->getExtendData($exception),
'tables' => [
- 'GET Data' => $_GET,
- 'POST Data' => $_POST,
- 'Files' => $_FILES,
- 'Cookies' => $_COOKIE,
- 'Session' => isset($_SESSION) ? $_SESSION : [],
- 'Server/Request Data' => $_SERVER,
- 'Environment Variables' => $_ENV,
- 'ThinkPHP Constants' => $this->getConst(),
+ 'GET Data' => $this->app->request->get(),
+ 'POST Data' => $this->app->request->post(),
+ 'Files' => $this->app->request->file(),
+ 'Cookies' => $this->app->request->cookie(),
+ 'Session' => $this->app->exists('session') ? $this->app->session->all() : [],
+ 'Server/Request Data' => $this->app->request->server(),
],
];
} else {
@@ -166,48 +186,54 @@ class Handle
'message' => $this->getMessage($exception),
];
- if (!Container::get('app')->config('show_error_msg')) {
+ if (!$this->app->config->get('app.show_error_msg')) {
// 不显示详细错误信息
- $data['message'] = Container::get('app')->config('error_message');
+ $data['message'] = $this->app->config->get('app.error_message');
}
}
- //保留一层
- while (ob_get_level() > 1) {
- ob_end_clean();
+ return $data;
+ }
+
+ /**
+ * @access protected
+ * @param Throwable $exception
+ * @return Response
+ */
+ protected function convertExceptionToResponse(Throwable $exception): Response
+ {
+ if (!$this->isJson) {
+ $response = Response::create($this->renderExceptionContent($exception));
+ } else {
+ $response = Response::create($this->convertExceptionToArray($exception), 'json');
}
- $data['echo'] = ob_get_clean();
-
- ob_start();
- extract($data);
- include Container::get('app')->config('exception_tmpl');
-
- // 获取并清空缓存
- $content = ob_get_clean();
- $response = Response::create($content, 'html');
-
if ($exception instanceof HttpException) {
$statusCode = $exception->getStatusCode();
$response->header($exception->getHeaders());
}
- if (!isset($statusCode)) {
- $statusCode = 500;
- }
- $response->code($statusCode);
+ return $response->code($statusCode ?? 500);
+ }
- return $response;
+ protected function renderExceptionContent(Throwable $exception): string
+ {
+ ob_start();
+ $data = $this->convertExceptionToArray($exception);
+ extract($data);
+ include $this->app->config->get('app.exception_tmpl') ?: __DIR__ . '/../../tpl/think_exception.tpl';
+
+ return ob_get_clean();
}
/**
* 获取错误编码
* ErrorException则使用错误级别作为错误编码
* @access protected
- * @param \Exception $exception
+ * @param Throwable $exception
* @return integer 错误编码
*/
- protected function getCode(Exception $exception)
+ protected function getCode(Throwable $exception)
{
$code = $exception->getCode();
@@ -222,18 +248,18 @@ class Handle
* 获取错误信息
* ErrorException则使用错误级别作为错误编码
* @access protected
- * @param \Exception $exception
+ * @param Throwable $exception
* @return string 错误信息
*/
- protected function getMessage(Exception $exception)
+ protected function getMessage(Throwable $exception): string
{
$message = $exception->getMessage();
- if (PHP_SAPI == 'cli') {
+ if ($this->app->runningInConsole()) {
return $message;
}
- $lang = Container::get('lang');
+ $lang = $this->app->lang;
if (strpos($message, ':')) {
$name = strstr($message, ':', true);
@@ -252,17 +278,17 @@ class Handle
* 获取出错文件内容
* 获取错误的前9行和后9行
* @access protected
- * @param \Exception $exception
+ * @param Throwable $exception
* @return array 错误文件内容
*/
- protected function getSourceCode(Exception $exception)
+ protected function getSourceCode(Throwable $exception): array
{
// 读取前9行和后9行
$line = $exception->getLine();
$first = ($line - 9 > 0) ? $line - 9 : 1;
try {
- $contents = file($exception->getFile());
+ $contents = file($exception->getFile()) ?: [];
$source = [
'first' => $first,
'source' => array_slice($contents, $first - 1, 19),
@@ -278,10 +304,10 @@ class Handle
* 获取异常扩展信息
* 用于非调试模式html返回类型显示
* @access protected
- * @param \Exception $exception
+ * @param Throwable $exception
* @return array 异常类定义的扩展数据
*/
- protected function getExtendData(Exception $exception)
+ protected function getExtendData(Throwable $exception): array
{
$data = [];
@@ -294,13 +320,13 @@ class Handle
/**
* 获取常量列表
- * @access private
+ * @access protected
* @return array 常量列表
*/
- private static function getConst()
+ protected function getConst(): array
{
$const = get_defined_constants(true);
- return isset($const['user']) ? $const['user'] : [];
+ return $const['user'] ?? [];
}
}
diff --git a/thinkphp/library/think/exception/HttpException.php b/vendor/topthink/framework/src/think/exception/HttpException.php
old mode 100755
new mode 100644
similarity index 78%
rename from thinkphp/library/think/exception/HttpException.php
rename to vendor/topthink/framework/src/think/exception/HttpException.php
index 01a27fc23..45302e587
--- a/thinkphp/library/think/exception/HttpException.php
+++ b/vendor/topthink/framework/src/think/exception/HttpException.php
@@ -2,21 +2,27 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\exception;
+use Exception;
+
+/**
+ * HTTP异常
+ */
class HttpException extends \RuntimeException
{
private $statusCode;
private $headers;
- public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = [], $code = 0)
+ public function __construct(int $statusCode, string $message = '', Exception $previous = null, array $headers = [], $code = 0)
{
$this->statusCode = $statusCode;
$this->headers = $headers;
diff --git a/thinkphp/library/think/exception/HttpResponseException.php b/vendor/topthink/framework/src/think/exception/HttpResponseException.php
old mode 100755
new mode 100644
similarity index 88%
rename from thinkphp/library/think/exception/HttpResponseException.php
rename to vendor/topthink/framework/src/think/exception/HttpResponseException.php
index 52972867b..607813d97
--- a/thinkphp/library/think/exception/HttpResponseException.php
+++ b/vendor/topthink/framework/src/think/exception/HttpResponseException.php
@@ -2,17 +2,21 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\exception;
use think\Response;
+/**
+ * HTTP响应异常
+ */
class HttpResponseException extends \RuntimeException
{
/**
diff --git a/vendor/topthink/framework/src/think/exception/InvalidArgumentException.php b/vendor/topthink/framework/src/think/exception/InvalidArgumentException.php
new file mode 100644
index 000000000..8ccd6f6a7
--- /dev/null
+++ b/vendor/topthink/framework/src/think/exception/InvalidArgumentException.php
@@ -0,0 +1,22 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+namespace think\exception;
+
+use Psr\Cache\InvalidArgumentException as Psr6CacheInvalidArgumentInterface;
+use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentInterface;
+
+/**
+ * 非法数据异常
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInvalidArgumentInterface, SimpleCacheInvalidArgumentInterface
+{
+}
diff --git a/thinkphp/library/think/exception/RouteNotFoundException.php b/vendor/topthink/framework/src/think/exception/RouteNotFoundException.php
old mode 100755
new mode 100644
similarity index 85%
rename from thinkphp/library/think/exception/RouteNotFoundException.php
rename to vendor/topthink/framework/src/think/exception/RouteNotFoundException.php
index d22e3a637..7a2ee8790
--- a/thinkphp/library/think/exception/RouteNotFoundException.php
+++ b/vendor/topthink/framework/src/think/exception/RouteNotFoundException.php
@@ -2,15 +2,19 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\exception;
+/**
+ * 路由未定义异常
+ */
class RouteNotFoundException extends HttpException
{
diff --git a/thinkphp/library/think/exception/ValidateException.php b/vendor/topthink/framework/src/think/exception/ValidateException.php
old mode 100755
new mode 100644
similarity index 78%
rename from thinkphp/library/think/exception/ValidateException.php
rename to vendor/topthink/framework/src/think/exception/ValidateException.php
index e3f843745..89b4e4d58
--- a/thinkphp/library/think/exception/ValidateException.php
+++ b/vendor/topthink/framework/src/think/exception/ValidateException.php
@@ -2,24 +2,27 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\exception;
+/**
+ * 数据验证异常
+ */
class ValidateException extends \RuntimeException
{
protected $error;
- public function __construct($error, $code = 0)
+ public function __construct($error)
{
$this->error = $error;
- $this->message = is_array($error) ? implode("\n\r", $error) : $error;
- $this->code = $code;
+ $this->message = is_array($error) ? implode(PHP_EOL, $error) : $error;
}
/**
diff --git a/vendor/topthink/framework/src/think/facade/App.php b/vendor/topthink/framework/src/think/facade/App.php
new file mode 100644
index 000000000..e9f81050e
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/App.php
@@ -0,0 +1,59 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\App
+ * @package think\facade
+ * @mixin \think\App
+ * @method static \think\Service|null register(\think\Service|string $service, bool $force = false) 注册服务
+ * @method static mixed bootService(\think\Service $service) 执行服务
+ * @method static \think\Service|null getService(string|\think\Service $service) 获取服务
+ * @method static \think\App debug(bool $debug = true) 开启应用调试模式
+ * @method static bool isDebug() 是否为调试模式
+ * @method static \think\App setNamespace(string $namespace) 设置应用命名空间
+ * @method static string getNamespace() 获取应用类库命名空间
+ * @method static string version() 获取框架版本
+ * @method static string getRootPath() 获取应用根目录
+ * @method static string getBasePath() 获取应用基础目录
+ * @method static string getAppPath() 获取当前应用目录
+ * @method static mixed setAppPath(string $path) 设置应用目录
+ * @method static string getRuntimePath() 获取应用运行时目录
+ * @method static void setRuntimePath(string $path) 设置runtime目录
+ * @method static string getThinkPath() 获取核心框架目录
+ * @method static string getConfigPath() 获取应用配置目录
+ * @method static string getConfigExt() 获取配置后缀
+ * @method static float getBeginTime() 获取应用开启时间
+ * @method static integer getBeginMem() 获取应用初始内存占用
+ * @method static \think\App initialize() 初始化应用
+ * @method static bool initialized() 是否初始化过
+ * @method static void loadLangPack(string $langset) 加载语言包
+ * @method static void boot() 引导应用
+ * @method static void loadEvent(array $event) 注册应用事件
+ * @method static string parseClass(string $layer, string $name) 解析应用类的类名
+ * @method static bool runningInConsole() 是否运行在命令行下
+ */
+class App extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'app';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Cache.php b/vendor/topthink/framework/src/think/facade/Cache.php
new file mode 100644
index 000000000..aac105d44
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Cache.php
@@ -0,0 +1,48 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\cache\Driver;
+use think\cache\TagSet;
+use think\Facade;
+
+/**
+ * @see \think\Cache
+ * @package think\facade
+ * @mixin \think\Cache
+ * @method static string|null getDefaultDriver() 默认驱动
+ * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取缓存配置
+ * @method static array getStoreConfig(string $store, string $name = null, null $default = null) 获取驱动配置
+ * @method static Driver store(string $name = null) 连接或者切换缓存
+ * @method static bool clear() 清空缓冲池
+ * @method static mixed get(string $key, mixed $default = null) 读取缓存
+ * @method static bool set(string $key, mixed $value, int|\DateTime $ttl = null) 写入缓存
+ * @method static bool delete(string $key) 删除缓存
+ * @method static iterable getMultiple(iterable $keys, mixed $default = null) 读取缓存
+ * @method static bool setMultiple(iterable $values, null|int|\DateInterval $ttl = null) 写入缓存
+ * @method static bool deleteMultiple(iterable $keys) 删除缓存
+ * @method static bool has(string $key) 判断缓存是否存在
+ * @method static TagSet tag(string|array $name) 缓存标签
+ */
+class Cache extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'cache';
+ }
+}
diff --git a/thinkphp/library/think/facade/Config.php b/vendor/topthink/framework/src/think/facade/Config.php
old mode 100755
new mode 100644
similarity index 63%
rename from thinkphp/library/think/facade/Config.php
rename to vendor/topthink/framework/src/think/facade/Config.php
index 8646d1271..4ce73dd6e
--- a/thinkphp/library/think/facade/Config.php
+++ b/vendor/topthink/framework/src/think/facade/Config.php
@@ -2,12 +2,13 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\facade;
@@ -15,12 +16,12 @@ use think\Facade;
/**
* @see \think\Config
+ * @package think\facade
* @mixin \think\Config
- * @method bool has(string $name) static 检测配置是否存在
- * @method array pull(string $name) static 获取一级配置
- * @method mixed get(string $name,mixed $default = null) static 获取配置参数
- * @method mixed set(string $name, mixed $value = null) static 设置配置参数
- * @method array reset(string $prefix ='') static 重置配置参数
+ * @method static array load(string $file, string $name = '') 加载配置文件(多种格式)
+ * @method static bool has(string $name) 检测配置是否存在
+ * @method static mixed get(string $name = null, mixed $default = null) 获取配置参数 为空则获取所有配置
+ * @method static array set(array $config, string $name = null) 设置配置参数 name为数组则为批量设置
*/
class Config extends Facade
{
diff --git a/vendor/topthink/framework/src/think/facade/Console.php b/vendor/topthink/framework/src/think/facade/Console.php
new file mode 100644
index 000000000..30dd935e9
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Console.php
@@ -0,0 +1,56 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Definition as InputDefinition;
+use think\console\Output;
+use think\console\output\driver\Buffer;
+use think\Facade;
+
+/**
+ * Class Console
+ * @package think\facade
+ * @mixin \think\Console
+ * @method static Output|Buffer call(string $command, array $parameters = [], string $driver = 'buffer')
+ * @method static int run() 执行当前的指令
+ * @method static int doRun(Input $input, Output $output) 执行指令
+ * @method static void setDefinition(InputDefinition $definition) 设置输入参数定义
+ * @method static InputDefinition The InputDefinition instance getDefinition() 获取输入参数定义
+ * @method static string A help message. getHelp() Gets the help message.
+ * @method static void setCatchExceptions(bool $boolean) 是否捕获异常
+ * @method static void setAutoExit(bool $boolean) 是否自动退出
+ * @method static string getLongVersion() 获取完整的版本号
+ * @method static void addCommands(array $commands) 添加指令集
+ * @method static Command|void addCommand(string|Command $command, string $name = '') 添加一个指令
+ * @method static Command getCommand(string $name) 获取指令
+ * @method static bool hasCommand(string $name) 某个指令是否存在
+ * @method static array getNamespaces() 获取所有的命名空间
+ * @method static string findNamespace(string $namespace) 查找注册命名空间中的名称或缩写。
+ * @method static Command find(string $name) 查找指令
+ * @method static Command[] all(string $namespace = null) 获取所有的指令
+ * @method static string extractNamespace(string $name, int $limit = 0) 返回命名空间部分
+ */
+class Console extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'console';
+ }
+}
diff --git a/thinkphp/library/think/facade/Cookie.php b/vendor/topthink/framework/src/think/facade/Cookie.php
old mode 100755
new mode 100644
similarity index 55%
rename from thinkphp/library/think/facade/Cookie.php
rename to vendor/topthink/framework/src/think/facade/Cookie.php
index 4d7cea250..960f4a3df
--- a/thinkphp/library/think/facade/Cookie.php
+++ b/vendor/topthink/framework/src/think/facade/Cookie.php
@@ -2,12 +2,13 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\facade;
@@ -15,15 +16,15 @@ use think\Facade;
/**
* @see \think\Cookie
+ * @package think\facade
* @mixin \think\Cookie
- * @method void init(array $config = []) static 初始化
- * @method bool has(string $name,string $prefix = null) static 判断Cookie数据
- * @method mixed prefix(string $prefix = '') static 设置或者获取cookie作用域(前缀)
- * @method mixed get(string $name,string $prefix = null) static Cookie获取
- * @method mixed set(string $name, mixed $value = null, mixed $option = null) static 设置Cookie
- * @method void forever(string $name, mixed $value = null, mixed $option = null) static 永久保存Cookie数据
- * @method void delete(string $name, string $prefix = null) static Cookie删除
- * @method void clear($prefix = null) static Cookie清空
+ * @method static mixed get(mixed $name = '', string $default = null) 获取cookie
+ * @method static bool has(string $name) 是否存在Cookie参数
+ * @method static void set(string $name, string $value, mixed $option = null) Cookie 设置
+ * @method static void forever(string $name, string $value = '', mixed $option = null) 永久保存Cookie数据
+ * @method static void delete(string $name) Cookie删除
+ * @method static array getCookie() 获取cookie保存数据
+ * @method static void save() 保存Cookie
*/
class Cookie extends Facade
{
diff --git a/vendor/topthink/framework/src/think/facade/Env.php b/vendor/topthink/framework/src/think/facade/Env.php
new file mode 100644
index 000000000..bed253805
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Env.php
@@ -0,0 +1,44 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\Env
+ * @package think\facade
+ * @mixin \think\Env
+ * @method static void load(string $file) 读取环境变量定义文件
+ * @method static mixed get(string $name = null, mixed $default = null) 获取环境变量值
+ * @method static void set(string|array $env, mixed $value = null) 设置环境变量值
+ * @method static bool has(string $name) 检测是否存在环境变量
+ * @method static void __set(string $name, mixed $value) 设置环境变量
+ * @method static mixed __get(string $name) 获取环境变量
+ * @method static bool __isset(string $name) 检测是否存在环境变量
+ * @method static void offsetSet($name, $value)
+ * @method static bool offsetExists($name)
+ * @method static mixed offsetUnset($name)
+ * @method static mixed offsetGet($name)
+ */
+class Env extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'env';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Event.php b/vendor/topthink/framework/src/think/facade/Event.php
new file mode 100644
index 000000000..c09d81667
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Event.php
@@ -0,0 +1,42 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\Event
+ * @package think\facade
+ * @mixin \think\Event
+ * @method static \think\Event listenEvents(array $events) 批量注册事件监听
+ * @method static \think\Event listen(string $event, mixed $listener, bool $first = false) 注册事件监听
+ * @method static bool hasListener(string $event) 是否存在事件监听
+ * @method static void remove(string $event) 移除事件监听
+ * @method static \think\Event bind(array $events) 指定事件别名标识 便于调用
+ * @method static \think\Event subscribe(mixed $subscriber) 注册事件订阅者
+ * @method static \think\Event observe(string|object $observer, null|string $prefix = '') 自动注册事件观察者
+ * @method static mixed trigger(string|object $event, mixed $params = null, bool $once = false) 触发事件
+ * @method static mixed until($event, $params = null) 触发事件(只获取一个有效返回值)
+ */
+class Event extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'event';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Filesystem.php b/vendor/topthink/framework/src/think/facade/Filesystem.php
new file mode 100644
index 000000000..53706a841
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Filesystem.php
@@ -0,0 +1,33 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+use think\filesystem\Driver;
+
+/**
+ * Class Filesystem
+ * @package think\facade
+ * @mixin \think\Filesystem
+ * @method static Driver disk(string $name = null) ,null|string
+ * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取缓存配置
+ * @method static array getDiskConfig(string $disk, null $name = null, null $default = null) 获取磁盘配置
+ * @method static string|null getDefaultDriver() 默认驱动
+ */
+class Filesystem extends Facade
+{
+ protected static function getFacadeClass()
+ {
+ return 'filesystem';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Lang.php b/vendor/topthink/framework/src/think/facade/Lang.php
new file mode 100644
index 000000000..b460fe2f9
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Lang.php
@@ -0,0 +1,41 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\Lang
+ * @package think\facade
+ * @mixin \think\Lang
+ * @method static void setLangSet(string $lang) 设置当前语言
+ * @method static string getLangSet() 获取当前语言
+ * @method static string defaultLangSet() 获取默认语言
+ * @method static array load(string|array $file, string $range = '') 加载语言定义(不区分大小写)
+ * @method static bool has(string|null $name, string $range = '') 判断是否存在语言定义(不区分大小写)
+ * @method static mixed get(string|null $name = null, array $vars = [], string $range = '') 获取语言定义(不区分大小写)
+ * @method static string detect(\think\Request $request) 自动侦测设置获取语言选择
+ * @method static void saveToCookie(\think\Cookie $cookie) 保存当前语言到Cookie
+ */
+class Lang extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'lang';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Log.php b/vendor/topthink/framework/src/think/facade/Log.php
new file mode 100644
index 000000000..7c43d37e7
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Log.php
@@ -0,0 +1,58 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+use think\log\Channel;
+use think\log\ChannelSet;
+
+/**
+ * @see \think\Log
+ * @package think\facade
+ * @mixin \think\Log
+ * @method static string|null getDefaultDriver() 默认驱动
+ * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取日志配置
+ * @method static array getChannelConfig(string $channel, null $name = null, null $default = null) 获取渠道配置
+ * @method static Channel|ChannelSet channel(string|array $name = null) driver() 的别名
+ * @method static mixed createDriver(string $name)
+ * @method static \think\Log clear(string|array $channel = '*') 清空日志信息
+ * @method static \think\Log close(string|array $channel = '*') 关闭本次请求日志写入
+ * @method static array getLog(string $channel = null) 获取日志信息
+ * @method static bool save() 保存日志信息
+ * @method static \think\Log record(mixed $msg, string $type = 'info', array $context = [], bool $lazy = true) 记录日志信息
+ * @method static \think\Log write(mixed $msg, string $type = 'info', array $context = []) 实时写入日志信息
+ * @method static Event listen($listener) 注册日志写入事件监听
+ * @method static void log(string $level, mixed $message, array $context = []) 记录日志信息
+ * @method static void emergency(mixed $message, array $context = []) 记录emergency信息
+ * @method static void alert(mixed $message, array $context = []) 记录警报信息
+ * @method static void critical(mixed $message, array $context = []) 记录紧急情况
+ * @method static void error(mixed $message, array $context = []) 记录错误信息
+ * @method static void warning(mixed $message, array $context = []) 记录warning信息
+ * @method static void notice(mixed $message, array $context = []) 记录notice信息
+ * @method static void info(mixed $message, array $context = []) 记录一般信息
+ * @method static void debug(mixed $message, array $context = []) 记录调试信息
+ * @method static void sql(mixed $message, array $context = []) 记录sql信息
+ * @method static mixed __call($method, $parameters)
+ */
+class Log extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'log';
+ }
+}
diff --git a/thinkphp/library/think/facade/Middleware.php b/vendor/topthink/framework/src/think/facade/Middleware.php
old mode 100755
new mode 100644
similarity index 50%
rename from thinkphp/library/think/facade/Middleware.php
rename to vendor/topthink/framework/src/think/facade/Middleware.php
index 5e4cac747..4203f821e
--- a/thinkphp/library/think/facade/Middleware.php
+++ b/vendor/topthink/framework/src/think/facade/Middleware.php
@@ -2,12 +2,13 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\facade;
@@ -15,12 +16,17 @@ use think\Facade;
/**
* @see \think\Middleware
+ * @package think\facade
* @mixin \think\Middleware
- * @method void import(array $middlewares = []) static 批量设置中间件
- * @method void add(mixed $middleware) static 添加中间件到队列
- * @method void unshift(mixed $middleware) static 添加中间件到队列开头
- * @method array all() static 获取中间件队列
- * @method \think\Response dispatch(\think\Request $request) static 执行中间件调度
+ * @method static void import(array $middlewares = [], string $type = 'global') 导入中间件
+ * @method static void add(mixed $middleware, string $type = 'global') 注册中间件
+ * @method static void route(mixed $middleware) 注册路由中间件
+ * @method static void controller(mixed $middleware) 注册控制器中间件
+ * @method static mixed unshift(mixed $middleware, string $type = 'global') 注册中间件到开始位置
+ * @method static array all(string $type = 'global') 获取注册的中间件
+ * @method static Pipeline pipeline(string $type = 'global') 调度管道
+ * @method static mixed end(\think\Response $response) 结束调度
+ * @method static \think\Response handleException(\think\Request $passable, \Throwable $e) 异常处理
*/
class Middleware extends Facade
{
diff --git a/vendor/topthink/framework/src/think/facade/Request.php b/vendor/topthink/framework/src/think/facade/Request.php
new file mode 100644
index 000000000..6531f4678
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Request.php
@@ -0,0 +1,134 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+use think\file\UploadedFile;
+use think\route\Rule;
+
+/**
+ * @see \think\Request
+ * @package think\facade
+ * @mixin \think\Request
+ * @method static \think\Request setDomain(string $domain) 设置当前包含协议的域名
+ * @method static string domain(bool $port = false) 获取当前包含协议的域名
+ * @method static string rootDomain() 获取当前根域名
+ * @method static \think\Request setSubDomain(string $domain) 设置当前泛域名的值
+ * @method static string subDomain() 获取当前子域名
+ * @method static \think\Request setPanDomain(string $domain) 设置当前泛域名的值
+ * @method static string panDomain() 获取当前泛域名的值
+ * @method static \think\Request setUrl(string $url) 设置当前完整URL 包括QUERY_STRING
+ * @method static string url(bool $complete = false) 获取当前完整URL 包括QUERY_STRING
+ * @method static \think\Request setBaseUrl(string $url) 设置当前URL 不含QUERY_STRING
+ * @method static string baseUrl(bool $complete = false) 获取当前URL 不含QUERY_STRING
+ * @method static string baseFile(bool $complete = false) 获取当前执行的文件 SCRIPT_NAME
+ * @method static \think\Request setRoot(string $url) 设置URL访问根地址
+ * @method static string root(bool $complete = false) 获取URL访问根地址
+ * @method static string rootUrl() 获取URL访问根目录
+ * @method static \think\Request setPathinfo(string $pathinfo) 设置当前请求的pathinfo
+ * @method static string pathinfo() 获取当前请求URL的pathinfo信息(含URL后缀)
+ * @method static string ext() 当前URL的访问后缀
+ * @method static integer|float time(bool $float = false) 获取当前请求的时间
+ * @method static string type() 当前请求的资源类型
+ * @method static void mimeType(string|array $type, string $val = '') 设置资源类型
+ * @method static \think\Request setMethod(string $method) 设置请求类型
+ * @method static string method(bool $origin = false) 当前的请求类型
+ * @method static bool isGet() 是否为GET请求
+ * @method static bool isPost() 是否为POST请求
+ * @method static bool isPut() 是否为PUT请求
+ * @method static bool isDelete() 是否为DELTE请求
+ * @method static bool isHead() 是否为HEAD请求
+ * @method static bool isPatch() 是否为PATCH请求
+ * @method static bool isOptions() 是否为OPTIONS请求
+ * @method static bool isCli() 是否为cli
+ * @method static bool isCgi() 是否为cgi
+ * @method static mixed param(string|array $name = '', mixed $default = null, string|array $filter = '') 获取当前请求的参数
+ * @method static \think\Request setRule(Rule $rule) 设置路由变量
+ * @method static Rule|null rule() 获取当前路由对象
+ * @method static \think\Request setRoute(array $route) 设置路由变量
+ * @method static mixed route(string|array $name = '', mixed $default = null, string|array $filter = '') 获取路由参数
+ * @method static mixed get(string|array $name = '', mixed $default = null, string|array $filter = '') 获取GET参数
+ * @method static mixed middleware(mixed $name, mixed $default = null) 获取中间件传递的参数
+ * @method static mixed post(string|array $name = '', mixed $default = null, string|array $filter = '') 获取POST参数
+ * @method static mixed put(string|array $name = '', mixed $default = null, string|array $filter = '') 获取PUT参数
+ * @method static mixed delete(mixed $name = '', mixed $default = null, string|array $filter = '') 设置获取DELETE参数
+ * @method static mixed patch(mixed $name = '', mixed $default = null, string|array $filter = '') 设置获取PATCH参数
+ * @method static mixed request(string|array $name = '', mixed $default = null, string|array $filter = '') 获取request变量
+ * @method static mixed env(string $name = '', string $default = null) 获取环境变量
+ * @method static mixed session(string $name = '', string $default = null) 获取session数据
+ * @method static mixed cookie(mixed $name = '', string $default = null, string|array $filter = '') 获取cookie参数
+ * @method static mixed server(string $name = '', string $default = '') 获取server参数
+ * @method static null|array|UploadedFile file(string $name = '') 获取上传的文件信息
+ * @method static string|array header(string $name = '', string $default = null) 设置或者获取当前的Header
+ * @method static mixed input(array $data = [], string|false $name = '', mixed $default = null, string|array $filter = '') 获取变量 支持过滤和默认值
+ * @method static mixed filter(mixed $filter = null) 设置或获取当前的过滤规则
+ * @method static mixed filterValue(mixed &$value, mixed $key, array $filters) 递归过滤给定的值
+ * @method static bool has(string $name, string $type = 'param', bool $checkEmpty = false) 是否存在某个请求参数
+ * @method static array only(array $name, mixed $data = 'param', string|array $filter = '') 获取指定的参数
+ * @method static mixed except(array $name, string $type = 'param') 排除指定参数获取
+ * @method static bool isSsl() 当前是否ssl
+ * @method static bool isJson() 当前是否JSON请求
+ * @method static bool isAjax(bool $ajax = false) 当前是否Ajax请求
+ * @method static bool isPjax(bool $pjax = false) 当前是否Pjax请求
+ * @method static string ip() 获取客户端IP地址
+ * @method static boolean isValidIP(string $ip, string $type = '') 检测是否是合法的IP地址
+ * @method static string ip2bin(string $ip) 将IP地址转换为二进制字符串
+ * @method static bool isMobile() 检测是否使用手机访问
+ * @method static string scheme() 当前URL地址中的scheme参数
+ * @method static string query() 当前请求URL地址中的query参数
+ * @method static \think\Request setHost(string $host) 设置当前请求的host(包含端口)
+ * @method static string host(bool $strict = false) 当前请求的host
+ * @method static int port() 当前请求URL地址中的port参数
+ * @method static string protocol() 当前请求 SERVER_PROTOCOL
+ * @method static int remotePort() 当前请求 REMOTE_PORT
+ * @method static string contentType() 当前请求 HTTP_CONTENT_TYPE
+ * @method static string secureKey() 获取当前请求的安全Key
+ * @method static \think\Request setController(string $controller) 设置当前的控制器名
+ * @method static \think\Request setAction(string $action) 设置当前的操作名
+ * @method static string controller(bool $convert = false) 获取当前的控制器名
+ * @method static string action(bool $convert = false) 获取当前的操作名
+ * @method static string getContent() 设置或者获取当前请求的content
+ * @method static string getInput() 获取当前请求的php://input
+ * @method static string buildToken(string $name = '__token__', mixed $type = 'md5') 生成请求令牌
+ * @method static bool checkToken(string $token = '__token__', array $data = []) 检查请求令牌
+ * @method static \think\Request withMiddleware(array $middleware) 设置在中间件传递的数据
+ * @method static \think\Request withGet(array $get) 设置GET数据
+ * @method static \think\Request withPost(array $post) 设置POST数据
+ * @method static \think\Request withCookie(array $cookie) 设置COOKIE数据
+ * @method static \think\Request withSession(Session $session) 设置SESSION数据
+ * @method static \think\Request withServer(array $server) 设置SERVER数据
+ * @method static \think\Request withHeader(array $header) 设置HEADER数据
+ * @method static \think\Request withEnv(Env $env) 设置ENV数据
+ * @method static \think\Request withInput(string $input) 设置php://input数据
+ * @method static \think\Request withFiles(array $files) 设置文件上传数据
+ * @method static \think\Request withRoute(array $route) 设置ROUTE变量
+ * @method static mixed __set(string $name, mixed $value) 设置中间传递数据
+ * @method static mixed __get(string $name) 获取中间传递数据的值
+ * @method static boolean __isset(string $name) 检测中间传递数据的值
+ * @method static bool offsetExists($name)
+ * @method static mixed offsetGet($name)
+ * @method static mixed offsetSet($name, $value)
+ * @method static mixed offsetUnset($name)
+ */
+class Request extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'request';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Route.php b/vendor/topthink/framework/src/think/facade/Route.php
new file mode 100644
index 000000000..46bd7469c
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Route.php
@@ -0,0 +1,83 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+use think\route\Dispatch;
+use think\route\Domain;
+use think\route\Rule;
+use think\route\RuleGroup;
+use think\route\RuleItem;
+use think\route\RuleName;
+use think\route\Url as UrlBuild;
+
+/**
+ * @see \think\Route
+ * @package think\facade
+ * @mixin \think\Route
+ * @method static mixed config(string $name = null)
+ * @method static \think\Route lazy(bool $lazy = true) 设置路由域名及分组(包括资源路由)是否延迟解析
+ * @method static void setTestMode(bool $test) 设置路由为测试模式
+ * @method static bool isTest() 检查路由是否为测试模式
+ * @method static \think\Route mergeRuleRegex(bool $merge = true) 设置路由域名及分组(包括资源路由)是否合并解析
+ * @method static void setGroup(RuleGroup $group) 设置当前分组
+ * @method static RuleGroup getGroup(string $name = null) 获取指定标识的路由分组 不指定则获取当前分组
+ * @method static \think\Route pattern(array $pattern) 注册变量规则
+ * @method static \think\Route option(array $option) 注册路由参数
+ * @method static Domain domain(string|array $name, mixed $rule = null) 注册域名路由
+ * @method static array getDomains() 获取域名
+ * @method static RuleName getRuleName() 获取RuleName对象
+ * @method static \think\Route bind(string $bind, string $domain = null) 设置路由绑定
+ * @method static array getBind() 读取路由绑定信息
+ * @method static string|null getDomainBind(string $domain = null) 读取路由绑定
+ * @method static RuleItem[] getName(string $name = null, string $domain = null, string $method = '*') 读取路由标识
+ * @method static void import(array $name) 批量导入路由标识
+ * @method static void setName(string $name, RuleItem $ruleItem, bool $first = false) 注册路由标识
+ * @method static void setRule(string $rule, RuleItem $ruleItem = null) 保存路由规则
+ * @method static RuleItem[] getRule(string $rule) 读取路由
+ * @method static array getRuleList() 读取路由列表
+ * @method static void clear() 清空路由规则
+ * @method static RuleItem rule(string $rule, mixed $route = null, string $method = '*') 注册路由规则
+ * @method static \think\Route setCrossDomainRule(Rule $rule, string $method = '*') 设置跨域有效路由规则
+ * @method static RuleGroup group(string|\Closure $name, mixed $route = null) 注册路由分组
+ * @method static RuleItem any(string $rule, mixed $route) 注册路由
+ * @method static RuleItem get(string $rule, mixed $route) 注册GET路由
+ * @method static RuleItem post(string $rule, mixed $route) 注册POST路由
+ * @method static RuleItem put(string $rule, mixed $route) 注册PUT路由
+ * @method static RuleItem delete(string $rule, mixed $route) 注册DELETE路由
+ * @method static RuleItem patch(string $rule, mixed $route) 注册PATCH路由
+ * @method static RuleItem options(string $rule, mixed $route) 注册OPTIONS路由
+ * @method static Resource resource(string $rule, string $route) 注册资源路由
+ * @method static RuleItem view(string $rule, string $template = '', array $vars = []) 注册视图路由
+ * @method static RuleItem redirect(string $rule, string $route = '', int $status = 301) 注册重定向路由
+ * @method static \think\Route rest(string|array $name, array|bool $resource = []) rest方法定义和修改
+ * @method static array|null getRest(string $name = null) 获取rest方法定义的参数
+ * @method static RuleItem miss(string|Closure $route, string $method = '*') 注册未匹配路由规则后的处理
+ * @method static Response dispatch(\think\Request $request, Closure|bool $withRoute = true) 路由调度
+ * @method static Dispatch|false check() 检测URL路由
+ * @method static Dispatch url(string $url) 默认URL解析
+ * @method static UrlBuild buildUrl(string $url = '', array $vars = []) URL生成 支持路由反射
+ * @method static RuleGroup __call(string $method, array $args) 设置全局的路由分组参数
+ */
+class Route extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'route';
+ }
+}
diff --git a/thinkphp/library/think/facade/Env.php b/vendor/topthink/framework/src/think/facade/Session.php
old mode 100755
new mode 100644
similarity index 66%
rename from thinkphp/library/think/facade/Env.php
rename to vendor/topthink/framework/src/think/facade/Session.php
index 5d0472443..68bf99362
--- a/thinkphp/library/think/facade/Env.php
+++ b/vendor/topthink/framework/src/think/facade/Session.php
@@ -2,25 +2,26 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\facade;
use think\Facade;
/**
- * @see \think\Env
- * @mixin \think\Env
- * @method void load(string $file) static 读取环境变量定义文件
- * @method mixed get(string $name = null, mixed $default = null) static 获取环境变量值
- * @method void set(mixed $env, string $value = null) static 设置环境变量值
+ * @see \think\Session
+ * @package think\facade
+ * @mixin \think\Session
+ * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取Session配置
+ * @method static string|null getDefaultDriver() 默认驱动
*/
-class Env extends Facade
+class Session extends Facade
{
/**
* 获取当前Facade对应类名(或者已经绑定的容器对象标识)
@@ -29,6 +30,6 @@ class Env extends Facade
*/
protected static function getFacadeClass()
{
- return 'env';
+ return 'session';
}
}
diff --git a/vendor/topthink/framework/src/think/facade/Validate.php b/vendor/topthink/framework/src/think/facade/Validate.php
new file mode 100644
index 000000000..6db6d34a4
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Validate.php
@@ -0,0 +1,95 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\Validate
+ * @package think\facade
+ * @mixin \think\Validate
+ * @method static void setLang(\think\Lang $lang) 设置Lang对象
+ * @method static void setDb(\think\Db $db) 设置Db对象
+ * @method static void setRequest(\think\Request $request) 设置Request对象
+ * @method static \think\Validate rule(string|array $name, mixed $rule = '') 添加字段验证规则
+ * @method static \think\Validate extend(string $type, callable $callback = null, string $message = null) 注册验证(类型)规则
+ * @method static void setTypeMsg(string|array $type, string $msg = null) 设置验证规则的默认提示信息
+ * @method static Validate message(array $message) 设置提示信息
+ * @method static \think\Validate scene(string $name) 设置验证场景
+ * @method static bool hasScene(string $name) 判断是否存在某个验证场景
+ * @method static \think\Validate batch(bool $batch = true) 设置批量验证
+ * @method static \think\Validate failException(bool $fail = true) 设置验证失败后是否抛出异常
+ * @method static \think\Validate only(array $fields) 指定需要验证的字段列表
+ * @method static \think\Validate remove(string|array $field, mixed $rule = null) 移除某个字段的验证规则
+ * @method static \think\Validate append(string|array $field, mixed $rule = null) 追加某个字段的验证规则
+ * @method static bool check(array $data, array $rules = []) 数据自动验证
+ * @method static bool checkRule(mixed $value, mixed $rules) 根据验证规则验证数据
+ * @method static bool confirm(mixed $value, mixed $rule, array $data = [], string $field = '') 验证是否和某个字段的值一致
+ * @method static bool different(mixed $value, mixed $rule, array $data = []) 验证是否和某个字段的值是否不同
+ * @method static bool egt(mixed $value, mixed $rule, array $data = []) 验证是否大于等于某个值
+ * @method static bool gt(mixed $value, mixed $rule, array $data = []) 验证是否大于某个值
+ * @method static bool elt(mixed $value, mixed $rule, array $data = []) 验证是否小于等于某个值
+ * @method static bool lt(mixed $value, mixed $rule, array $data = []) 验证是否小于某个值
+ * @method static bool eq(mixed $value, mixed $rule) 验证是否等于某个值
+ * @method static bool must(mixed $value, mixed $rule = null) 必须验证
+ * @method static bool is(mixed $value, string $rule, array $data = []) 验证字段值是否为有效格式
+ * @method static bool token(mixed $value, mixed $rule, array $data) 验证表单令牌
+ * @method static bool activeUrl(mixed $value, mixed $rule = 'MX') 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型
+ * @method static bool ip(mixed $value, mixed $rule = 'ipv4') 验证是否有效IP
+ * @method static bool fileExt(mixed $file, mixed $rule) 验证上传文件后缀
+ * @method static bool fileMime(mixed $file, mixed $rule) 验证上传文件类型
+ * @method static bool fileSize(mixed $file, mixed $rule) 验证上传文件大小
+ * @method static bool image(mixed $file, mixed $rule) 验证图片的宽高及类型
+ * @method static bool dateFormat(mixed $value, mixed $rule) 验证时间和日期是否符合指定格式
+ * @method static bool unique(mixed $value, mixed $rule, array $data = [], string $field = '') 验证是否唯一
+ * @method static bool filter(mixed $value, mixed $rule) 使用filter_var方式验证
+ * @method static bool requireIf(mixed $value, mixed $rule, array $data = []) 验证某个字段等于某个值的时候必须
+ * @method static bool requireCallback(mixed $value, mixed $rule, array $data = []) 通过回调方法验证某个字段是否必须
+ * @method static bool requireWith(mixed $value, mixed $rule, array $data = []) 验证某个字段有值的情况下必须
+ * @method static bool requireWithout(mixed $value, mixed $rule, array $data = []) 验证某个字段没有值的情况下必须
+ * @method static bool in(mixed $value, mixed $rule) 验证是否在范围内
+ * @method static bool notIn(mixed $value, mixed $rule) 验证是否不在某个范围
+ * @method static bool between(mixed $value, mixed $rule) between验证数据
+ * @method static bool notBetween(mixed $value, mixed $rule) 使用notbetween验证数据
+ * @method static bool length(mixed $value, mixed $rule) 验证数据长度
+ * @method static bool max(mixed $value, mixed $rule) 验证数据最大长度
+ * @method static bool min(mixed $value, mixed $rule) 验证数据最小长度
+ * @method static bool after(mixed $value, mixed $rule, array $data = []) 验证日期
+ * @method static bool before(mixed $value, mixed $rule, array $data = []) 验证日期
+ * @method static bool afterWith(mixed $value, mixed $rule, array $data = []) 验证日期
+ * @method static bool beforeWith(mixed $value, mixed $rule, array $data = []) 验证日期
+ * @method static bool expire(mixed $value, mixed $rule) 验证有效期
+ * @method static bool allowIp(mixed $value, mixed $rule) 验证IP许可
+ * @method static bool denyIp(mixed $value, mixed $rule) 验证IP禁用
+ * @method static bool regex(mixed $value, mixed $rule) 使用正则验证数据
+ * @method static array|string getError() 获取错误信息
+ * @method static bool __call(string $method, array $args) 动态方法 直接调用is方法进行验证
+ */
+class Validate extends Facade
+{
+ /**
+ * 始终创建新的对象实例
+ * @var bool
+ */
+ protected static $alwaysNewInstance = true;
+
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'validate';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/View.php b/vendor/topthink/framework/src/think/facade/View.php
new file mode 100644
index 000000000..acde3b577
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/View.php
@@ -0,0 +1,42 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\View
+ * @package think\facade
+ * @mixin \think\View
+ * @method static \think\View engine(string $type = null) 获取模板引擎
+ * @method static \think\View assign(string|array $name, mixed $value = null) 模板变量赋值
+ * @method static \think\View filter(\think\Callable $filter = null) 视图过滤
+ * @method static string fetch(string $template = '', array $vars = []) 解析和获取模板内容 用于输出
+ * @method static string display(string $content, array $vars = []) 渲染内容输出
+ * @method static mixed __set(string $name, mixed $value) 模板变量赋值
+ * @method static mixed __get(string $name) 取得模板显示变量的值
+ * @method static bool __isset(string $name) 检测模板变量是否设置
+ * @method static string|null getDefaultDriver() 默认驱动
+ */
+class View extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'view';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/file/UploadedFile.php b/vendor/topthink/framework/src/think/file/UploadedFile.php
new file mode 100644
index 000000000..7dff766e1
--- /dev/null
+++ b/vendor/topthink/framework/src/think/file/UploadedFile.php
@@ -0,0 +1,143 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\file;
+
+use think\exception\FileException;
+use think\File;
+
+class UploadedFile extends File
+{
+
+ private $test = false;
+ private $originalName;
+ private $mimeType;
+ private $error;
+
+ public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null, bool $test = false)
+ {
+ $this->originalName = $originalName;
+ $this->mimeType = $mimeType ?: 'application/octet-stream';
+ $this->test = $test;
+ $this->error = $error ?: UPLOAD_ERR_OK;
+
+ parent::__construct($path, UPLOAD_ERR_OK === $this->error);
+ }
+
+ public function isValid(): bool
+ {
+ $isOk = UPLOAD_ERR_OK === $this->error;
+
+ return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname());
+ }
+
+ /**
+ * 上传文件
+ * @access public
+ * @param string $directory 保存路径
+ * @param string|null $name 保存的文件名
+ * @return File
+ */
+ public function move(string $directory, string $name = null): File
+ {
+ if ($this->isValid()) {
+ if ($this->test) {
+ return parent::move($directory, $name);
+ }
+
+ $target = $this->getTargetFile($directory, $name);
+
+ set_error_handler(function ($type, $msg) use (&$error) {
+ $error = $msg;
+ });
+
+ $moved = move_uploaded_file($this->getPathname(), (string) $target);
+ restore_error_handler();
+ if (!$moved) {
+ throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
+ }
+
+ @chmod((string) $target, 0666 & ~umask());
+
+ return $target;
+ }
+
+ throw new FileException($this->getErrorMessage());
+ }
+
+ /**
+ * 获取错误信息
+ * @access public
+ * @return string
+ */
+ protected function getErrorMessage(): string
+ {
+ switch ($this->error) {
+ case 1:
+ case 2:
+ $message = 'upload File size exceeds the maximum value';
+ break;
+ case 3:
+ $message = 'only the portion of file is uploaded';
+ break;
+ case 4:
+ $message = 'no file to uploaded';
+ break;
+ case 6:
+ $message = 'upload temp dir not found';
+ break;
+ case 7:
+ $message = 'file write error';
+ break;
+ default:
+ $message = 'unknown upload error';
+ }
+
+ return $message;
+ }
+
+ /**
+ * 获取上传文件类型信息
+ * @return string
+ */
+ public function getOriginalMime(): string
+ {
+ return $this->mimeType;
+ }
+
+ /**
+ * 上传文件名
+ * @return string
+ */
+ public function getOriginalName(): string
+ {
+ return $this->originalName;
+ }
+
+ /**
+ * 获取上传文件扩展名
+ * @return string
+ */
+ public function getOriginalExtension(): string
+ {
+ return pathinfo($this->originalName, PATHINFO_EXTENSION);
+ }
+
+ /**
+ * 获取文件扩展名
+ * @return string
+ */
+ public function extension(): string
+ {
+ return $this->getOriginalExtension();
+ }
+}
diff --git a/vendor/topthink/framework/src/think/filesystem/CacheStore.php b/vendor/topthink/framework/src/think/filesystem/CacheStore.php
new file mode 100644
index 000000000..0a62399e0
--- /dev/null
+++ b/vendor/topthink/framework/src/think/filesystem/CacheStore.php
@@ -0,0 +1,54 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\filesystem;
+
+use League\Flysystem\Cached\Storage\AbstractCache;
+use Psr\SimpleCache\CacheInterface;
+
+class CacheStore extends AbstractCache
+{
+ protected $store;
+
+ protected $key;
+
+ protected $expire;
+
+ public function __construct(CacheInterface $store, $key = 'flysystem', $expire = null)
+ {
+ $this->key = $key;
+ $this->store = $store;
+ $this->expire = $expire;
+ }
+
+ /**
+ * Store the cache.
+ */
+ public function save()
+ {
+ $contents = $this->getForStorage();
+
+ $this->store->set($this->key, $contents, $this->expire);
+ }
+
+ /**
+ * Load the cache.
+ */
+ public function load()
+ {
+ $contents = $this->store->get($this->key);
+
+ if (!is_null($contents)) {
+ $this->setFromStorage($contents);
+ }
+ }
+}
diff --git a/vendor/topthink/framework/src/think/filesystem/Driver.php b/vendor/topthink/framework/src/think/filesystem/Driver.php
new file mode 100644
index 000000000..67129592c
--- /dev/null
+++ b/vendor/topthink/framework/src/think/filesystem/Driver.php
@@ -0,0 +1,133 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\filesystem;
+
+use League\Flysystem\AdapterInterface;
+use League\Flysystem\Adapter\AbstractAdapter;
+use League\Flysystem\Cached\CachedAdapter;
+use League\Flysystem\Cached\Storage\Memory as MemoryStore;
+use League\Flysystem\Filesystem;
+use think\Cache;
+use think\File;
+
+/**
+ * Class Driver
+ * @package think\filesystem
+ * @mixin Filesystem
+ */
+abstract class Driver
+{
+
+ /** @var Cache */
+ protected $cache;
+
+ /** @var Filesystem */
+ protected $filesystem;
+
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [];
+
+ public function __construct(Cache $cache, array $config)
+ {
+ $this->cache = $cache;
+ $this->config = array_merge($this->config, $config);
+
+ $adapter = $this->createAdapter();
+ $this->filesystem = $this->createFilesystem($adapter);
+ }
+
+ protected function createCacheStore($config)
+ {
+ if (true === $config) {
+ return new MemoryStore;
+ }
+
+ return new CacheStore(
+ $this->cache->store($config['store']),
+ $config['prefix'] ?? 'flysystem',
+ $config['expire'] ?? null
+ );
+ }
+
+ abstract protected function createAdapter(): AdapterInterface;
+
+ protected function createFilesystem(AdapterInterface $adapter): Filesystem
+ {
+ if (!empty($this->config['cache'])) {
+ $adapter = new CachedAdapter($adapter, $this->createCacheStore($this->config['cache']));
+ }
+
+ $config = array_intersect_key($this->config, array_flip(['visibility', 'disable_asserts', 'url']));
+
+ return new Filesystem($adapter, count($config) > 0 ? $config : null);
+ }
+
+ /**
+ * 获取文件完整路径
+ * @param string $path
+ * @return string
+ */
+ public function path(string $path): string
+ {
+ $adapter = $this->filesystem->getAdapter();
+
+ if ($adapter instanceof AbstractAdapter) {
+ return $adapter->applyPathPrefix($path);
+ }
+
+ return $path;
+ }
+
+ /**
+ * 保存文件
+ * @param string $path 路径
+ * @param File $file 文件
+ * @param null|string|\Closure $rule 文件名规则
+ * @param array $options 参数
+ * @return bool|string
+ */
+ public function putFile(string $path, File $file, $rule = null, array $options = [])
+ {
+ return $this->putFileAs($path, $file, $file->hashName($rule), $options);
+ }
+
+ /**
+ * 指定文件名保存文件
+ * @param string $path 路径
+ * @param File $file 文件
+ * @param string $name 文件名
+ * @param array $options 参数
+ * @return bool|string
+ */
+ public function putFileAs(string $path, File $file, string $name, array $options = [])
+ {
+ $stream = fopen($file->getRealPath(), 'r');
+ $path = trim($path . '/' . $name, '/');
+
+ $result = $this->putStream($path, $stream, $options);
+
+ if (is_resource($stream)) {
+ fclose($stream);
+ }
+
+ return $result ? $path : false;
+ }
+
+ public function __call($method, $parameters)
+ {
+ return $this->filesystem->$method(...$parameters);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/filesystem/driver/Local.php b/vendor/topthink/framework/src/think/filesystem/driver/Local.php
new file mode 100644
index 000000000..c10ccc3b6
--- /dev/null
+++ b/vendor/topthink/framework/src/think/filesystem/driver/Local.php
@@ -0,0 +1,44 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\filesystem\driver;
+
+use League\Flysystem\AdapterInterface;
+use League\Flysystem\Adapter\Local as LocalAdapter;
+use think\filesystem\Driver;
+
+class Local extends Driver
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [
+ 'root' => '',
+ ];
+
+ protected function createAdapter(): AdapterInterface
+ {
+ $permissions = $this->config['permissions'] ?? [];
+
+ $links = ($this->config['links'] ?? null) === 'skip'
+ ? LocalAdapter::SKIP_LINKS
+ : LocalAdapter::DISALLOW_LINKS;
+
+ return new LocalAdapter(
+ $this->config['root'],
+ LOCK_EX,
+ $links,
+ $permissions
+ );
+ }
+}
diff --git a/vendor/topthink/framework/src/think/initializer/BootService.php b/vendor/topthink/framework/src/think/initializer/BootService.php
new file mode 100644
index 000000000..bab6d3904
--- /dev/null
+++ b/vendor/topthink/framework/src/think/initializer/BootService.php
@@ -0,0 +1,26 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\initializer;
+
+use think\App;
+
+/**
+ * 启动系统服务
+ */
+class BootService
+{
+ public function init(App $app)
+ {
+ $app->boot();
+ }
+}
diff --git a/vendor/topthink/framework/src/think/initializer/Error.php b/vendor/topthink/framework/src/think/initializer/Error.php
new file mode 100644
index 000000000..201d94732
--- /dev/null
+++ b/vendor/topthink/framework/src/think/initializer/Error.php
@@ -0,0 +1,117 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\initializer;
+
+use think\App;
+use think\console\Output as ConsoleOutput;
+use think\exception\ErrorException;
+use think\exception\Handle;
+use Throwable;
+
+/**
+ * 错误和异常处理
+ */
+class Error
+{
+ /** @var App */
+ protected $app;
+
+ /**
+ * 注册异常处理
+ * @access public
+ * @param App $app
+ * @return void
+ */
+ public function init(App $app)
+ {
+ $this->app = $app;
+ error_reporting(E_ALL);
+ set_error_handler([$this, 'appError']);
+ set_exception_handler([$this, 'appException']);
+ register_shutdown_function([$this, 'appShutdown']);
+ }
+
+ /**
+ * Exception Handler
+ * @access public
+ * @param \Throwable $e
+ */
+ public function appException(Throwable $e): void
+ {
+ $handler = $this->getExceptionHandler();
+
+ $handler->report($e);
+
+ if ($this->app->runningInConsole()) {
+ $handler->renderForConsole(new ConsoleOutput, $e);
+ } else {
+ $handler->render($this->app->request, $e)->send();
+ }
+ }
+
+ /**
+ * Error Handler
+ * @access public
+ * @param integer $errno 错误编号
+ * @param string $errstr 详细错误信息
+ * @param string $errfile 出错的文件
+ * @param integer $errline 出错行号
+ * @throws ErrorException
+ */
+ public function appError(int $errno, string $errstr, string $errfile = '', int $errline = 0): void
+ {
+ $exception = new ErrorException($errno, $errstr, $errfile, $errline);
+
+ if (error_reporting() & $errno) {
+ // 将错误信息托管至 think\exception\ErrorException
+ throw $exception;
+ }
+ }
+
+ /**
+ * Shutdown Handler
+ * @access public
+ */
+ public function appShutdown(): void
+ {
+ if (!is_null($error = error_get_last()) && $this->isFatal($error['type'])) {
+ // 将错误信息托管至think\ErrorException
+ $exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']);
+
+ $this->appException($exception);
+ }
+ }
+
+ /**
+ * 确定错误类型是否致命
+ *
+ * @access protected
+ * @param int $type
+ * @return bool
+ */
+ protected function isFatal(int $type): bool
+ {
+ return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]);
+ }
+
+ /**
+ * Get an instance of the exception handler.
+ *
+ * @access protected
+ * @return Handle
+ */
+ protected function getExceptionHandler()
+ {
+ return $this->app->make(Handle::class);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/initializer/RegisterService.php b/vendor/topthink/framework/src/think/initializer/RegisterService.php
new file mode 100644
index 000000000..b682a0b0e
--- /dev/null
+++ b/vendor/topthink/framework/src/think/initializer/RegisterService.php
@@ -0,0 +1,48 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\initializer;
+
+use think\App;
+use think\service\ModelService;
+use think\service\PaginatorService;
+use think\service\ValidateService;
+
+/**
+ * 注册系统服务
+ */
+class RegisterService
+{
+
+ protected $services = [
+ PaginatorService::class,
+ ValidateService::class,
+ ModelService::class,
+ ];
+
+ public function init(App $app)
+ {
+ $file = $app->getRootPath() . 'vendor/services.php';
+
+ $services = $this->services;
+
+ if (is_file($file)) {
+ $services = array_merge($services, include $file);
+ }
+
+ foreach ($services as $service) {
+ if (class_exists($service)) {
+ $app->register($service);
+ }
+ }
+ }
+}
diff --git a/vendor/topthink/framework/src/think/log/Channel.php b/vendor/topthink/framework/src/think/log/Channel.php
new file mode 100644
index 000000000..1de96f1a5
--- /dev/null
+++ b/vendor/topthink/framework/src/think/log/Channel.php
@@ -0,0 +1,286 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\log;
+
+use Psr\Log\LoggerInterface;
+use think\contract\LogHandlerInterface;
+use think\Event;
+use think\event\LogRecord;
+use think\event\LogWrite;
+
+class Channel implements LoggerInterface
+{
+ protected $name;
+ protected $logger;
+ protected $event;
+
+ protected $lazy = true;
+ /**
+ * 日志信息
+ * @var array
+ */
+ protected $log = [];
+
+ /**
+ * 关闭日志
+ * @var array
+ */
+ protected $close = false;
+
+ /**
+ * 允许写入类型
+ * @var array
+ */
+ protected $allow = [];
+
+ public function __construct(string $name, LogHandlerInterface $logger, array $allow, bool $lazy = true, Event $event = null)
+ {
+ $this->name = $name;
+ $this->logger = $logger;
+ $this->allow = $allow;
+ $this->lazy = $lazy;
+ $this->event = $event;
+ }
+
+ /**
+ * 关闭通道
+ */
+ public function close()
+ {
+ $this->clear();
+ $this->close = true;
+ }
+
+ /**
+ * 清空日志
+ */
+ public function clear()
+ {
+ $this->log = [];
+ }
+
+ /**
+ * 记录日志信息
+ * @access public
+ * @param mixed $msg 日志信息
+ * @param string $type 日志级别
+ * @param array $context 替换内容
+ * @param bool $lazy
+ * @return $this
+ */
+ public function record($msg, string $type = 'info', array $context = [], bool $lazy = true)
+ {
+ if ($this->close || (!empty($this->allow) && !in_array($type, $this->allow))) {
+ return $this;
+ }
+
+ if (is_string($msg) && !empty($context)) {
+ $replace = [];
+ foreach ($context as $key => $val) {
+ $replace['{' . $key . '}'] = $val;
+ }
+
+ $msg = strtr($msg, $replace);
+ }
+
+ if (!empty($msg) || 0 === $msg) {
+ $this->log[$type][] = $msg;
+ if ($this->event) {
+ $this->event->trigger(new LogRecord($type, $msg));
+ }
+ }
+
+ if (!$this->lazy || !$lazy) {
+ $this->save();
+ }
+
+ return $this;
+ }
+
+ /**
+ * 实时写入日志信息
+ * @access public
+ * @param mixed $msg 调试信息
+ * @param string $type 日志级别
+ * @param array $context 替换内容
+ * @return $this
+ */
+ public function write($msg, string $type = 'info', array $context = [])
+ {
+ return $this->record($msg, $type, $context, false);
+ }
+
+ /**
+ * 获取日志信息
+ * @return array
+ */
+ public function getLog(): array
+ {
+ return $this->log;
+ }
+
+ /**
+ * 保存日志
+ * @return bool
+ */
+ public function save(): bool
+ {
+ $log = $this->log;
+ if ($this->event) {
+ $event = new LogWrite($this->name, $log);
+ $this->event->trigger($event);
+ $log = $event->log;
+ }
+
+ if ($this->logger->save($log)) {
+ $this->clear();
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function emergency($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function log($level, $message, array $context = [])
+ {
+ $this->record($message, $level, $context);
+ }
+
+ public function __call($method, $parameters)
+ {
+ $this->log($method, ...$parameters);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/log/ChannelSet.php b/vendor/topthink/framework/src/think/log/ChannelSet.php
new file mode 100644
index 000000000..6dcb0bdb0
--- /dev/null
+++ b/vendor/topthink/framework/src/think/log/ChannelSet.php
@@ -0,0 +1,39 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\log;
+
+use think\Log;
+
+/**
+ * Class ChannelSet
+ * @package think\log
+ * @mixin Channel
+ */
+class ChannelSet
+{
+ protected $log;
+ protected $channels;
+
+ public function __construct(Log $log, array $channels)
+ {
+ $this->log = $log;
+ $this->channels = $channels;
+ }
+
+ public function __call($method, $arguments)
+ {
+ foreach ($this->channels as $channel) {
+ $this->log->channel($channel)->{$method}(...$arguments);
+ }
+ }
+}
diff --git a/vendor/topthink/framework/src/think/log/driver/File.php b/vendor/topthink/framework/src/think/log/driver/File.php
new file mode 100644
index 000000000..e5682fc00
--- /dev/null
+++ b/vendor/topthink/framework/src/think/log/driver/File.php
@@ -0,0 +1,205 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\log\driver;
+
+use think\App;
+use think\contract\LogHandlerInterface;
+
+/**
+ * 本地化调试输出到文件
+ */
+class File implements LogHandlerInterface
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [
+ 'time_format' => 'c',
+ 'single' => false,
+ 'file_size' => 2097152,
+ 'path' => '',
+ 'apart_level' => [],
+ 'max_files' => 0,
+ 'json' => false,
+ 'json_options' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
+ 'format' => '[%s][%s] %s',
+ ];
+
+ // 实例化并传入参数
+ public function __construct(App $app, $config = [])
+ {
+ if (is_array($config)) {
+ $this->config = array_merge($this->config, $config);
+ }
+
+ if (empty($this->config['format'])) {
+ $this->config['format'] = '[%s][%s] %s';
+ }
+
+ if (empty($this->config['path'])) {
+ $this->config['path'] = $app->getRuntimePath() . 'log';
+ }
+
+ if (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) {
+ $this->config['path'] .= DIRECTORY_SEPARATOR;
+ }
+ }
+
+ /**
+ * 日志写入接口
+ * @access public
+ * @param array $log 日志信息
+ * @return bool
+ */
+ public function save(array $log): bool
+ {
+ $destination = $this->getMasterLogFile();
+
+ $path = dirname($destination);
+ !is_dir($path) && mkdir($path, 0755, true);
+
+ $info = [];
+
+ // 日志信息封装
+ $time = \DateTime::createFromFormat('0.u00 U', microtime())->setTimezone(new \DateTimeZone(date_default_timezone_get()))->format($this->config['time_format']);
+
+ foreach ($log as $type => $val) {
+ $message = [];
+ foreach ($val as $msg) {
+ if (!is_string($msg)) {
+ $msg = var_export($msg, true);
+ }
+
+ $message[] = $this->config['json'] ?
+ json_encode(['time' => $time, 'type' => $type, 'msg' => $msg], $this->config['json_options']) :
+ sprintf($this->config['format'], $time, $type, $msg);
+ }
+
+ if (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level'])) {
+ // 独立记录的日志级别
+ $filename = $this->getApartLevelFile($path, $type);
+ $this->write($message, $filename);
+ continue;
+ }
+
+ $info[$type] = $message;
+ }
+
+ if ($info) {
+ return $this->write($info, $destination);
+ }
+
+ return true;
+ }
+
+ /**
+ * 日志写入
+ * @access protected
+ * @param array $message 日志信息
+ * @param string $destination 日志文件
+ * @return bool
+ */
+ protected function write(array $message, string $destination): bool
+ {
+ // 检测日志文件大小,超过配置大小则备份日志文件重新生成
+ $this->checkLogSize($destination);
+
+ $info = [];
+
+ foreach ($message as $type => $msg) {
+ $info[$type] = is_array($msg) ? implode(PHP_EOL, $msg) : $msg;
+ }
+
+ $message = implode(PHP_EOL, $info) . PHP_EOL;
+
+ return error_log($message, 3, $destination);
+ }
+
+ /**
+ * 获取主日志文件名
+ * @access public
+ * @return string
+ */
+ protected function getMasterLogFile(): string
+ {
+
+ if ($this->config['max_files']) {
+ $files = glob($this->config['path'] . '*.log');
+
+ try {
+ if (count($files) > $this->config['max_files']) {
+ unlink($files[0]);
+ }
+ } catch (\Exception $e) {
+ //
+ }
+ }
+
+ if ($this->config['single']) {
+ $name = is_string($this->config['single']) ? $this->config['single'] : 'single';
+ $destination = $this->config['path'] . $name . '.log';
+ } else {
+
+ if ($this->config['max_files']) {
+ $filename = date('Ymd') . '.log';
+ } else {
+ $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . '.log';
+ }
+
+ $destination = $this->config['path'] . $filename;
+ }
+
+ return $destination;
+ }
+
+ /**
+ * 获取独立日志文件名
+ * @access public
+ * @param string $path 日志目录
+ * @param string $type 日志类型
+ * @return string
+ */
+ protected function getApartLevelFile(string $path, string $type): string
+ {
+
+ if ($this->config['single']) {
+ $name = is_string($this->config['single']) ? $this->config['single'] : 'single';
+
+ $name .= '_' . $type;
+ } elseif ($this->config['max_files']) {
+ $name = date('Ymd') . '_' . $type;
+ } else {
+ $name = date('d') . '_' . $type;
+ }
+
+ return $path . DIRECTORY_SEPARATOR . $name . '.log';
+ }
+
+ /**
+ * 检查日志文件大小并自动生成备份文件
+ * @access protected
+ * @param string $destination 日志文件
+ * @return void
+ */
+ protected function checkLogSize(string $destination): void
+ {
+ if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) {
+ try {
+ rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination));
+ } catch (\Exception $e) {
+ //
+ }
+ }
+ }
+}
diff --git a/thinkphp/library/think/log/driver/Socket.php b/vendor/topthink/framework/src/think/log/driver/Socket.php
old mode 100755
new mode 100644
similarity index 53%
rename from thinkphp/library/think/log/driver/Socket.php
rename to vendor/topthink/framework/src/think/log/driver/Socket.php
index 5e4f8bfd8..2cfb94339
--- a/thinkphp/library/think/log/driver/Socket.php
+++ b/vendor/topthink/framework/src/think/log/driver/Socket.php
@@ -2,36 +2,50 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: luofei614
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\log\driver;
+use Psr\Container\NotFoundExceptionInterface;
use think\App;
+use think\contract\LogHandlerInterface;
/**
* github: https://github.com/luofei614/SocketLog
* @author luofei614
*/
-class Socket
+class Socket implements LogHandlerInterface
{
- public $port = 1116; //SocketLog 服务的http的端口号
+ protected $app;
protected $config = [
// socket服务器地址
'host' => 'localhost',
+ // socket服务器端口
+ 'port' => 1116,
// 是否显示加载的文件列表
'show_included_files' => false,
// 日志强制记录到配置的client_id
'force_client_ids' => [],
// 限制允许读取日志的client_id
'allow_client_ids' => [],
- //输出到浏览器默认展开的日志级别
+ // 调试开关
+ 'debug' => false,
+ // 输出到浏览器时默认展开的日志级别
'expand_level' => ['debug'],
+ // 日志头渲染回调
+ 'format_head' => null,
+ // curl opt
+ 'curl_opt' => [
+ CURLOPT_CONNECTTIMEOUT => 1,
+ CURLOPT_TIMEOUT => 10,
+ ],
];
protected $css = [
@@ -43,12 +57,14 @@ class Socket
];
protected $allowForceClientIds = []; //配置强制推送且被授权的client_id
- protected $app;
+
+ protected $clientArg = [];
/**
* 架构函数
* @access public
- * @param array $config 缓存参数
+ * @param App $app
+ * @param array $config 缓存参数
*/
public function __construct(App $app, array $config = [])
{
@@ -57,15 +73,19 @@ class Socket
if (!empty($config)) {
$this->config = array_merge($this->config, $config);
}
+
+ if (!isset($config['debug'])) {
+ $this->config['debug'] = $app->isDebug();
+ }
}
/**
* 调试输出接口
* @access public
- * @param array $log 日志信息
+ * @param array $log 日志信息
* @return bool
*/
- public function save(array $log = [], $append = false)
+ public function save(array $log = []): bool
{
if (!$this->check()) {
return false;
@@ -73,33 +93,36 @@ class Socket
$trace = [];
- if ($this->app->isDebug()) {
- $runtime = round(microtime(true) - $this->app->getBeginTime(), 10);
- $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞';
- $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]';
- $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2);
- $memory_str = ' [内存消耗:' . $memory_use . 'kb]';
- $file_load = ' [文件加载:' . count(get_included_files()) . ']';
-
- if (isset($_SERVER['HTTP_HOST'])) {
- $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+ if ($this->config['debug']) {
+ if ($this->app->exists('request')) {
+ $currentUri = $this->app->request->url(true);
} else {
- $current_uri = 'cmd:' . implode(' ', $_SERVER['argv']);
+ $currentUri = 'cmd:' . implode(' ', $_SERVER['argv'] ?? []);
+ }
+
+ if (!empty($this->config['format_head'])) {
+ try {
+ $currentUri = $this->app->invoke($this->config['format_head'], [$currentUri]);
+ } catch (NotFoundExceptionInterface $notFoundException) {
+ // Ignore exception
+ }
}
// 基本信息
$trace[] = [
'type' => 'group',
- 'msg' => $current_uri . $time_str . $memory_str . $file_load,
+ 'msg' => $currentUri,
'css' => $this->css['page'],
];
}
+ $expandLevel = array_flip($this->config['expand_level']);
+
foreach ($log as $type => $val) {
$trace[] = [
- 'type' => in_array($type, $this->config['expand_level']) ? 'group' : 'groupCollapsed',
+ 'type' => isset($expandLevel[$type]) ? 'group' : 'groupCollapsed',
'msg' => '[ ' . $type . ' ]',
- 'css' => isset($this->css[$type]) ? $this->css[$type] : '',
+ 'css' => $this->css[$type] ?? '',
];
foreach ($val as $msg) {
@@ -148,18 +171,18 @@ class Socket
$tabid = $this->getClientArg('tabid');
- if (!$client_id = $this->getClientArg('client_id')) {
- $client_id = '';
+ if (!$clientId = $this->getClientArg('client_id')) {
+ $clientId = '';
}
if (!empty($this->allowForceClientIds)) {
//强制推送到多个client_id
- foreach ($this->allowForceClientIds as $force_client_id) {
- $client_id = $force_client_id;
- $this->sendToClient($tabid, $client_id, $trace, $force_client_id);
+ foreach ($this->allowForceClientIds as $forceClientId) {
+ $clientId = $forceClientId;
+ $this->sendToClient($tabid, $clientId, $trace, $forceClientId);
}
} else {
- $this->sendToClient($tabid, $client_id, $trace, '');
+ $this->sendToClient($tabid, $clientId, $trace, '');
}
return true;
@@ -170,25 +193,30 @@ class Socket
* @access protected
* @author Zjmainstay
* @param $tabid
- * @param $client_id
+ * @param $clientId
* @param $logs
- * @param $force_client_id
+ * @param $forceClientId
*/
- protected function sendToClient($tabid, $client_id, $logs, $force_client_id)
+ protected function sendToClient($tabid, $clientId, $logs, $forceClientId)
{
$logs = [
'tabid' => $tabid,
- 'client_id' => $client_id,
+ 'client_id' => $clientId,
'logs' => $logs,
- 'force_client_id' => $force_client_id,
+ 'force_client_id' => $forceClientId,
];
- $msg = @json_encode($logs);
- $address = '/' . $client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁
+ $msg = json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR);
+ $address = '/' . $clientId; //将client_id作为地址, server端通过地址判断将日志发布给谁
- $this->send($this->config['host'], $msg, $address);
+ $this->send($this->config['host'], $this->config['port'], $msg, $address);
}
+ /**
+ * 检测客户授权
+ * @access protected
+ * @return bool
+ */
protected function check()
{
$tabid = $this->getClientArg('tabid');
@@ -199,17 +227,17 @@ class Socket
}
//用户认证
- $allow_client_ids = $this->config['allow_client_ids'];
+ $allowClientIds = $this->config['allow_client_ids'];
- if (!empty($allow_client_ids)) {
+ if (!empty($allowClientIds)) {
//通过数组交集得出授权强制推送的client_id
- $this->allowForceClientIds = array_intersect($allow_client_ids, $this->config['force_client_ids']);
+ $this->allowForceClientIds = array_intersect($allowClientIds, $this->config['force_client_ids']);
if (!$tabid && count($this->allowForceClientIds)) {
return true;
}
- $client_id = $this->getClientArg('client_id');
- if (!in_array($client_id, $allow_client_ids)) {
+ $clientId = $this->getClientArg('client_id');
+ if (!in_array($clientId, $allowClientIds)) {
return false;
}
} else {
@@ -219,53 +247,58 @@ class Socket
return true;
}
- protected function getClientArg($name)
+ /**
+ * 获取客户参数
+ * @access protected
+ * @param string $name
+ * @return string
+ */
+ protected function getClientArg(string $name)
{
- static $args = [];
-
- $key = 'HTTP_USER_AGENT';
-
- if (isset($_SERVER['HTTP_SOCKETLOG'])) {
- $key = 'HTTP_SOCKETLOG';
+ if (!$this->app->exists('request')) {
+ return '';
}
- if (!isset($_SERVER[$key])) {
- return;
- }
-
- if (empty($args)) {
- if (!preg_match('/SocketLog\((.*?)\)/', $_SERVER[$key], $match)) {
- $args = ['tabid' => null];
- return;
+ if (empty($this->clientArg)) {
+ if (empty($socketLog = $this->app->request->header('socketlog'))) {
+ if (empty($socketLog = $this->app->request->header('User-Agent'))) {
+ return '';
+ }
}
- parse_str($match[1], $args);
+
+ if (!preg_match('/SocketLog\((.*?)\)/', $socketLog, $match)) {
+ $this->clientArg = ['tabid' => null, 'client_id' => null];
+ return '';
+ }
+ parse_str($match[1] ?? '', $this->clientArg);
}
- if (isset($args[$name])) {
- return $args[$name];
+ if (isset($this->clientArg[$name])) {
+ return $this->clientArg[$name];
}
- return;
+ return '';
}
/**
* @access protected
- * @param string $host - $host of socket server
- * @param string $message - 发送的消息
- * @param string $address - 地址
+ * @param string $host - $host of socket server
+ * @param int $port - $port of socket server
+ * @param string $message - 发送的消息
+ * @param string $address - 地址
* @return bool
*/
- protected function send($host, $message = '', $address = '/')
+ protected function send($host, $port, $message = '', $address = '/')
{
- $url = 'http://' . $host . ':' . $this->port . $address;
+ $url = 'http://' . $host . ':' . $port . $address;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $message);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1);
- curl_setopt($ch, CURLOPT_TIMEOUT, 10);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->config['curl_opt'][CURLOPT_CONNECTTIMEOUT] ?? 1);
+ curl_setopt($ch, CURLOPT_TIMEOUT, $this->config['curl_opt'][CURLOPT_TIMEOUT] ?? 10);
$headers = [
"Content-Type: application/json;charset=UTF-8",
@@ -275,5 +308,4 @@ class Socket
return curl_exec($ch);
}
-
}
diff --git a/vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php b/vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php
new file mode 100644
index 000000000..b7ab842c4
--- /dev/null
+++ b/vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php
@@ -0,0 +1,63 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\middleware;
+
+use Closure;
+use think\Config;
+use think\Request;
+use think\Response;
+
+/**
+ * 跨域请求支持
+ */
+class AllowCrossDomain
+{
+ protected $cookieDomain;
+
+ protected $header = [
+ 'Access-Control-Allow-Credentials' => 'true',
+ 'Access-Control-Max-Age' => 1800,
+ 'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE, OPTIONS',
+ 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With',
+ ];
+
+ public function __construct(Config $config)
+ {
+ $this->cookieDomain = $config->get('cookie.domain', '');
+ }
+
+ /**
+ * 允许跨域请求
+ * @access public
+ * @param Request $request
+ * @param Closure $next
+ * @param array $header
+ * @return Response
+ */
+ public function handle($request, Closure $next, ? array $header = [])
+ {
+ $header = !empty($header) ? array_merge($this->header, $header) : $this->header;
+
+ if (!isset($header['Access-Control-Allow-Origin'])) {
+ $origin = $request->header('origin');
+
+ if ($origin && ('' == $this->cookieDomain || strpos($origin, $this->cookieDomain))) {
+ $header['Access-Control-Allow-Origin'] = $origin;
+ } else {
+ $header['Access-Control-Allow-Origin'] = '*';
+ }
+ }
+
+ return $next($request)->header($header);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/middleware/CheckRequestCache.php b/vendor/topthink/framework/src/think/middleware/CheckRequestCache.php
new file mode 100644
index 000000000..b1143519c
--- /dev/null
+++ b/vendor/topthink/framework/src/think/middleware/CheckRequestCache.php
@@ -0,0 +1,183 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\middleware;
+
+use Closure;
+use think\Cache;
+use think\Config;
+use think\Request;
+use think\Response;
+
+/**
+ * 请求缓存处理
+ */
+class CheckRequestCache
+{
+ /**
+ * 缓存对象
+ * @var Cache
+ */
+ protected $cache;
+
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [
+ // 请求缓存规则 true为自动规则
+ 'request_cache_key' => true,
+ // 请求缓存有效期
+ 'request_cache_expire' => null,
+ // 全局请求缓存排除规则
+ 'request_cache_except' => [],
+ // 请求缓存的Tag
+ 'request_cache_tag' => '',
+ ];
+
+ public function __construct(Cache $cache, Config $config)
+ {
+ $this->cache = $cache;
+ $this->config = array_merge($this->config, $config->get('route'));
+ }
+
+ /**
+ * 设置当前地址的请求缓存
+ * @access public
+ * @param Request $request
+ * @param Closure $next
+ * @param mixed $cache
+ * @return Response
+ */
+ public function handle($request, Closure $next, $cache = null)
+ {
+ if ($request->isGet() && false !== $cache) {
+ if (false === $this->config['request_cache_key']) {
+ // 关闭当前缓存
+ $cache = false;
+ }
+
+ $cache = $cache ?? $this->getRequestCache($request);
+
+ if ($cache) {
+ if (is_array($cache)) {
+ [$key, $expire, $tag] = array_pad($cache, 3, null);
+ } else {
+ $key = md5($request->url(true));
+ $expire = $cache;
+ $tag = null;
+ }
+
+ $key = $this->parseCacheKey($request, $key);
+
+ if (strtotime($request->server('HTTP_IF_MODIFIED_SINCE', '')) + $expire > $request->server('REQUEST_TIME')) {
+ // 读取缓存
+ return Response::create()->code(304);
+ } elseif (($hit = $this->cache->get($key)) !== null) {
+ [$content, $header, $when] = $hit;
+ if (null === $expire || $when + $expire > $request->server('REQUEST_TIME')) {
+ return Response::create($content)->header($header);
+ }
+ }
+ }
+ }
+
+ $response = $next($request);
+
+ if (isset($key) && 200 == $response->getCode() && $response->isAllowCache()) {
+ $header = $response->getHeader();
+ $header['Cache-Control'] = 'max-age=' . $expire . ',must-revalidate';
+ $header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT';
+ $header['Expires'] = gmdate('D, d M Y H:i:s', time() + $expire) . ' GMT';
+
+ $this->cache->tag($tag)->set($key, [$response->getContent(), $header, time()], $expire);
+ }
+
+ return $response;
+ }
+
+ /**
+ * 读取当前地址的请求缓存信息
+ * @access protected
+ * @param Request $request
+ * @return mixed
+ */
+ protected function getRequestCache($request)
+ {
+ $key = $this->config['request_cache_key'];
+ $expire = $this->config['request_cache_expire'];
+ $except = $this->config['request_cache_except'];
+ $tag = $this->config['request_cache_tag'];
+
+ foreach ($except as $rule) {
+ if (0 === stripos($request->url(), $rule)) {
+ return;
+ }
+ }
+
+ return [$key, $expire, $tag];
+ }
+
+ /**
+ * 读取当前地址的请求缓存信息
+ * @access protected
+ * @param Request $request
+ * @param mixed $key
+ * @return null|string
+ */
+ protected function parseCacheKey($request, $key)
+ {
+ if ($key instanceof \Closure) {
+ $key = call_user_func($key, $request);
+ }
+
+ if (false === $key) {
+ // 关闭当前缓存
+ return;
+ }
+
+ if (true === $key) {
+ // 自动缓存功能
+ $key = '__URL__';
+ } elseif (strpos($key, '|')) {
+ [$key, $fun] = explode('|', $key);
+ }
+
+ // 特殊规则替换
+ if (false !== strpos($key, '__')) {
+ $key = str_replace(['__CONTROLLER__', '__ACTION__', '__URL__'], [$request->controller(), $request->action(), md5($request->url(true))], $key);
+ }
+
+ if (false !== strpos($key, ':')) {
+ $param = $request->param();
+
+ foreach ($param as $item => $val) {
+ if (is_string($val) && false !== strpos($key, ':' . $item)) {
+ $key = str_replace(':' . $item, (string) $val, $key);
+ }
+ }
+ } elseif (strpos($key, ']')) {
+ if ('[' . $request->ext() . ']' == $key) {
+ // 缓存某个后缀的请求
+ $key = md5($request->url());
+ } else {
+ return;
+ }
+ }
+
+ if (isset($fun)) {
+ $key = $fun($key);
+ }
+
+ return $key;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/middleware/FormTokenCheck.php b/vendor/topthink/framework/src/think/middleware/FormTokenCheck.php
new file mode 100644
index 000000000..efbb77b17
--- /dev/null
+++ b/vendor/topthink/framework/src/think/middleware/FormTokenCheck.php
@@ -0,0 +1,45 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\middleware;
+
+use Closure;
+use think\exception\ValidateException;
+use think\Request;
+use think\Response;
+
+/**
+ * 表单令牌支持
+ */
+class FormTokenCheck
+{
+
+ /**
+ * 表单令牌检测
+ * @access public
+ * @param Request $request
+ * @param Closure $next
+ * @param string $token 表单令牌Token名称
+ * @return Response
+ */
+ public function handle(Request $request, Closure $next, string $token = null)
+ {
+ $check = $request->checkToken($token ?: '__token__');
+
+ if (false === $check) {
+ throw new ValidateException('invalid token');
+ }
+
+ return $next($request);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/middleware/LoadLangPack.php b/vendor/topthink/framework/src/think/middleware/LoadLangPack.php
new file mode 100644
index 000000000..478e29c90
--- /dev/null
+++ b/vendor/topthink/framework/src/think/middleware/LoadLangPack.php
@@ -0,0 +1,61 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\middleware;
+
+use Closure;
+use think\App;
+use think\Lang;
+use think\Request;
+use think\Response;
+
+/**
+ * 多语言加载
+ */
+class LoadLangPack
+{
+ protected $app;
+
+ protected $lang;
+
+ public function __construct(App $app, Lang $lang)
+ {
+ $this->app = $app;
+ $this->lang = $lang;
+ }
+
+ /**
+ * 路由初始化(路由规则注册)
+ * @access public
+ * @param Request $request
+ * @param Closure $next
+ * @return Response
+ */
+ public function handle($request, Closure $next)
+ {
+ // 自动侦测当前语言
+ $langset = $this->lang->detect($request);
+
+ if ($this->lang->defaultLangSet() != $langset) {
+ // 加载系统语言包
+ $this->lang->load([
+ $this->app->getThinkPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.php',
+ ]);
+
+ $this->app->LoadLangPack($langset);
+ }
+
+ $this->lang->saveToCookie($this->app->cookie);
+
+ return $next($request);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/middleware/SessionInit.php b/vendor/topthink/framework/src/think/middleware/SessionInit.php
new file mode 100644
index 000000000..3cb2fad96
--- /dev/null
+++ b/vendor/topthink/framework/src/think/middleware/SessionInit.php
@@ -0,0 +1,80 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\middleware;
+
+use Closure;
+use think\App;
+use think\Request;
+use think\Response;
+use think\Session;
+
+/**
+ * Session初始化
+ */
+class SessionInit
+{
+
+ /** @var App */
+ protected $app;
+
+ /** @var Session */
+ protected $session;
+
+ public function __construct(App $app, Session $session)
+ {
+ $this->app = $app;
+ $this->session = $session;
+ }
+
+ /**
+ * Session初始化
+ * @access public
+ * @param Request $request
+ * @param Closure $next
+ * @return Response
+ */
+ public function handle($request, Closure $next)
+ {
+ // Session初始化
+ $varSessionId = $this->app->config->get('session.var_session_id');
+ $cookieName = $this->session->getName();
+
+ if ($varSessionId && $request->request($varSessionId)) {
+ $sessionId = $request->request($varSessionId);
+ } else {
+ $sessionId = $request->cookie($cookieName);
+ }
+
+ if ($sessionId) {
+ $this->session->setId($sessionId);
+ }
+
+ $this->session->init();
+
+ $request->withSession($this->session);
+
+ /** @var Response $response */
+ $response = $next($request);
+
+ $response->setSession($this->session);
+
+ $this->app->cookie->set($cookieName, $this->session->getId());
+
+ return $response;
+ }
+
+ public function end(Response $response)
+ {
+ $this->session->save();
+ }
+}
diff --git a/thinkphp/library/think/response/Download.php b/vendor/topthink/framework/src/think/response/File.php
old mode 100755
new mode 100644
similarity index 77%
rename from thinkphp/library/think/response/Download.php
rename to vendor/topthink/framework/src/think/response/File.php
index d5fcb444c..1e45f2f48
--- a/thinkphp/library/think/response/Download.php
+++ b/vendor/topthink/framework/src/think/response/File.php
@@ -2,25 +2,35 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\response;
use think\Exception;
use think\Response;
-class Download extends Response
+/**
+ * File Response
+ */
+class File extends Response
{
protected $expire = 360;
protected $name;
protected $mimeType;
protected $isContent = false;
- protected $openinBrower = false;
+ protected $force = true;
+
+ public function __construct($data = '', int $code = 200)
+ {
+ $this->init($data, $code);
+ }
+
/**
* 处理数据
* @access protected
@@ -34,7 +44,9 @@ class Download extends Response
throw new Exception('file not exists:' . $data);
}
- ob_end_clean();
+ while (ob_get_level() > 0) {
+ ob_end_clean();
+ }
if (!empty($this->name)) {
$name = $this->name;
@@ -53,15 +65,14 @@ class Download extends Response
$this->header['Pragma'] = 'public';
$this->header['Content-Type'] = $mimeType ?: 'application/octet-stream';
$this->header['Cache-control'] = 'max-age=' . $this->expire;
- $this->header['Content-Disposition'] = $this->openinBrower ? 'inline' : 'attachment; filename="' . $name . '"';
+ $this->header['Content-Disposition'] = ($this->force ? 'attachment; ' : '') . 'filename="' . $name . '"';
$this->header['Content-Length'] = $size;
$this->header['Content-Transfer-Encoding'] = 'binary';
$this->header['Expires'] = gmdate("D, d M Y H:i:s", time() + $this->expire) . ' GMT';
$this->lastModified(gmdate('D, d M Y H:i:s', time()) . ' GMT');
- $data = $this->isContent ? $data : file_get_contents($data);
- return $data;
+ return $this->isContent ? $data : file_get_contents($data);
}
/**
@@ -70,7 +81,7 @@ class Download extends Response
* @param bool $content
* @return $this
*/
- public function isContent($content = true)
+ public function isContent(bool $content = true)
{
$this->isContent = $content;
return $this;
@@ -82,7 +93,7 @@ class Download extends Response
* @param integer $expire 有效期
* @return $this
*/
- public function expire($expire)
+ public function expire(int $expire)
{
$this->expire = $expire;
return $this;
@@ -94,19 +105,31 @@ class Download extends Response
* @param string $filename 文件名
* @return $this
*/
- public function mimeType($mimeType)
+ public function mimeType(string $mimeType)
{
$this->mimeType = $mimeType;
return $this;
}
+ /**
+ * 设置文件强制下载
+ * @access public
+ * @param bool $force 强制浏览器下载
+ * @return $this
+ */
+ public function force(bool $force)
+ {
+ $this->force = $force;
+ return $this;
+ }
+
/**
* 获取文件类型信息
* @access public
* @param string $filename 文件名
* @return string
*/
- protected function getMimeType($filename)
+ protected function getMimeType(string $filename): string
{
if (!empty($this->mimeType)) {
return $this->mimeType;
@@ -124,7 +147,7 @@ class Download extends Response
* @param bool $extension 后缀自动识别
* @return $this
*/
- public function name($filename, $extension = true)
+ public function name(string $filename, bool $extension = true)
{
$this->name = $filename;
@@ -134,15 +157,4 @@ class Download extends Response
return $this;
}
-
- /**
- * 设置是否在浏览器中显示文件
- * @access public
- * @param bool $openinBrower 是否在浏览器中显示文件
- * @return $this
- */
- public function openinBrower($openinBrower) {
- $this->openinBrower = $openinBrower;
- return $this;
- }
}
diff --git a/thinkphp/library/think/response/Jump.php b/vendor/topthink/framework/src/think/response/Html.php
old mode 100755
new mode 100644
similarity index 63%
rename from thinkphp/library/think/response/Jump.php
rename to vendor/topthink/framework/src/think/response/Html.php
index 258448ca4..c158f7815
--- a/thinkphp/library/think/response/Jump.php
+++ b/vendor/topthink/framework/src/think/response/Html.php
@@ -2,31 +2,33 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\response;
+use think\Cookie;
use think\Response;
-class Jump extends Response
+/**
+ * Html Response
+ */
+class Html extends Response
{
+ /**
+ * 输出type
+ * @var string
+ */
protected $contentType = 'text/html';
- /**
- * 处理数据
- * @access protected
- * @param mixed $data 要处理的数据
- * @return mixed
- * @throws \Exception
- */
- protected function output($data)
+ public function __construct(Cookie $cookie, $data = '', int $code = 200)
{
- $data = $this->app['view']->fetch($this->options['jump_template'], $data);
- return $data;
+ $this->init($data, $code);
+ $this->cookie = $cookie;
}
}
diff --git a/thinkphp/library/think/response/Json.php b/vendor/topthink/framework/src/think/response/Json.php
old mode 100755
new mode 100644
similarity index 79%
rename from thinkphp/library/think/response/Json.php
rename to vendor/topthink/framework/src/think/response/Json.php
index aa5bbd6fa..a84501f55
--- a/thinkphp/library/think/response/Json.php
+++ b/vendor/topthink/framework/src/think/response/Json.php
@@ -2,17 +2,22 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\response;
+use think\Cookie;
use think\Response;
+/**
+ * Json Response
+ */
class Json extends Response
{
// 输出参数
@@ -22,14 +27,20 @@ class Json extends Response
protected $contentType = 'application/json';
+ public function __construct(Cookie $cookie, $data = '', int $code = 200)
+ {
+ $this->init($data, $code);
+ $this->cookie = $cookie;
+ }
+
/**
* 处理数据
* @access protected
* @param mixed $data 要处理的数据
- * @return mixed
+ * @return string
* @throws \Exception
*/
- protected function output($data)
+ protected function output($data): string
{
try {
// 返回JSON数据格式到客户端 包含状态信息
diff --git a/thinkphp/library/think/response/Jsonp.php b/vendor/topthink/framework/src/think/response/Jsonp.php
old mode 100755
new mode 100644
similarity index 71%
rename from thinkphp/library/think/response/Jsonp.php
rename to vendor/topthink/framework/src/think/response/Jsonp.php
index f69e88e1d..81d3a06e1
--- a/thinkphp/library/think/response/Jsonp.php
+++ b/vendor/topthink/framework/src/think/response/Jsonp.php
@@ -2,17 +2,23 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\response;
+use think\Cookie;
+use think\Request;
use think\Response;
+/**
+ * Jsonp Response
+ */
class Jsonp extends Response
{
// 输出参数
@@ -24,19 +30,29 @@ class Jsonp extends Response
protected $contentType = 'application/javascript';
+ protected $request;
+
+ public function __construct(Cookie $cookie, Request $request, $data = '', int $code = 200)
+ {
+ $this->init($data, $code);
+
+ $this->cookie = $cookie;
+ $this->request = $request;
+ }
+
/**
* 处理数据
* @access protected
* @param mixed $data 要处理的数据
- * @return mixed
+ * @return string
* @throws \Exception
*/
- protected function output($data)
+ protected function output($data): string
{
try {
// 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取]
- $var_jsonp_handler = $this->app['request']->param($this->options['var_jsonp_handler'], "");
- $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler'];
+ $varJsonpHandler = $this->request->param($this->options['var_jsonp_handler'], "");
+ $handler = !empty($varJsonpHandler) ? $varJsonpHandler : $this->options['default_jsonp_handler'];
$data = json_encode($data, $this->options['json_encode_param']);
diff --git a/thinkphp/library/think/response/Redirect.php b/vendor/topthink/framework/src/think/response/Redirect.php
old mode 100755
new mode 100644
similarity index 51%
rename from thinkphp/library/think/response/Redirect.php
rename to vendor/topthink/framework/src/think/response/Redirect.php
index 73729ce88..1f38764c9
--- a/thinkphp/library/think/response/Redirect.php
+++ b/vendor/topthink/framework/src/think/response/Redirect.php
@@ -2,28 +2,36 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\response;
+use think\Cookie;
+use think\Request;
use think\Response;
+use think\Session;
+/**
+ * Redirect Response
+ */
class Redirect extends Response
{
- protected $options = [];
+ protected $request;
- // URL参数
- protected $params = [];
-
- public function __construct($data = '', $code = 302, array $header = [], array $options = [])
+ public function __construct(Cookie $cookie, Request $request, Session $session, $data = '', int $code = 302)
{
- parent::__construct($data, $code, $header, $options);
+ $this->init((string) $data, $code);
+
+ $this->cookie = $cookie;
+ $this->request = $request;
+ $this->session = $session;
$this->cacheControl('no-cache,must-revalidate');
}
@@ -32,13 +40,13 @@ class Redirect extends Response
* 处理数据
* @access protected
* @param mixed $data 要处理的数据
- * @return mixed
+ * @return string
*/
- protected function output($data)
+ protected function output($data): string
{
- $this->header['Location'] = $this->getTargetUrl();
+ $this->header['Location'] = $data;
- return;
+ return '';
}
/**
@@ -50,40 +58,17 @@ class Redirect extends Response
*/
public function with($name, $value = null)
{
- $session = $this->app['session'];
-
if (is_array($name)) {
foreach ($name as $key => $val) {
- $session->flash($key, $val);
+ $this->session->flash($key, $val);
}
} else {
- $session->flash($name, $value);
+ $this->session->flash($name, $value);
}
return $this;
}
- /**
- * 获取跳转地址
- * @access public
- * @return string
- */
- public function getTargetUrl()
- {
- if (strpos($this->data, '://') || (0 === strpos($this->data, '/') && empty($this->params))) {
- return $this->data;
- } else {
- return $this->app['url']->build($this->data, $this->params);
- }
- }
-
- public function params($params = [])
- {
- $this->params = $params;
-
- return $this;
- }
-
/**
* 记住当前url后跳转
* @access public
@@ -91,7 +76,7 @@ class Redirect extends Response
*/
public function remember()
{
- $this->app['session']->set('redirect_url', $this->app['request']->url());
+ $this->session->set('redirect_url', $this->request->url());
return $this;
}
@@ -99,18 +84,13 @@ class Redirect extends Response
/**
* 跳转到上次记住的url
* @access public
- * @param string $url 闪存数据不存在时的跳转地址
* @return $this
*/
- public function restore($url = null)
+ public function restore()
{
- $session = $this->app['session'];
-
- if ($session->has('redirect_url')) {
- $this->data = $session->get('redirect_url');
- $session->delete('redirect_url');
- } elseif ($url) {
- $this->data = $url;
+ if ($this->session->has('redirect_url')) {
+ $this->data = $this->session->get('redirect_url');
+ $this->session->delete('redirect_url');
}
return $this;
diff --git a/vendor/topthink/framework/src/think/response/View.php b/vendor/topthink/framework/src/think/response/View.php
new file mode 100644
index 000000000..2c116c778
--- /dev/null
+++ b/vendor/topthink/framework/src/think/response/View.php
@@ -0,0 +1,151 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\response;
+
+use think\Cookie;
+use think\Response;
+use think\View as BaseView;
+
+/**
+ * View Response
+ */
+class View extends Response
+{
+ /**
+ * 输出参数
+ * @var array
+ */
+ protected $options = [];
+
+ /**
+ * 输出变量
+ * @var array
+ */
+ protected $vars = [];
+
+ /**
+ * 输出过滤
+ * @var mixed
+ */
+ protected $filter;
+
+ /**
+ * 输出type
+ * @var string
+ */
+ protected $contentType = 'text/html';
+
+ /**
+ * View对象
+ * @var BaseView
+ */
+ protected $view;
+
+ /**
+ * 是否内容渲染
+ * @var bool
+ */
+ protected $isContent = false;
+
+ public function __construct(Cookie $cookie, BaseView $view, $data = '', int $code = 200)
+ {
+ $this->init($data, $code);
+
+ $this->cookie = $cookie;
+ $this->view = $view;
+ }
+
+ /**
+ * 设置是否为内容渲染
+ * @access public
+ * @param bool $content
+ * @return $this
+ */
+ public function isContent(bool $content = true)
+ {
+ $this->isContent = $content;
+ return $this;
+ }
+
+ /**
+ * 处理数据
+ * @access protected
+ * @param mixed $data 要处理的数据
+ * @return string
+ */
+ protected function output($data): string
+ {
+ // 渲染模板输出
+ $this->view->filter($this->filter);
+ return $this->isContent ?
+ $this->view->display($data, $this->vars) :
+ $this->view->fetch($data, $this->vars);
+ }
+
+ /**
+ * 获取视图变量
+ * @access public
+ * @param string $name 模板变量
+ * @return mixed
+ */
+ public function getVars(string $name = null)
+ {
+ if (is_null($name)) {
+ return $this->vars;
+ } else {
+ return $this->vars[$name] ?? null;
+ }
+ }
+
+ /**
+ * 模板变量赋值
+ * @access public
+ * @param string|array $name 模板变量
+ * @param mixed $value 变量值
+ * @return $this
+ */
+ public function assign($name, $value = null)
+ {
+ if (is_array($name)) {
+ $this->vars = array_merge($this->vars, $name);
+ } else {
+ $this->vars[$name] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 视图内容过滤
+ * @access public
+ * @param callable $filter
+ * @return $this
+ */
+ public function filter(callable $filter = null)
+ {
+ $this->filter = $filter;
+ return $this;
+ }
+
+ /**
+ * 检查模板是否存在
+ * @access public
+ * @param string $name 模板名
+ * @return bool
+ */
+ public function exists(string $name): bool
+ {
+ return $this->view->exists($name);
+ }
+
+}
diff --git a/thinkphp/library/think/response/Xml.php b/vendor/topthink/framework/src/think/response/Xml.php
old mode 100755
new mode 100644
similarity index 85%
rename from thinkphp/library/think/response/Xml.php
rename to vendor/topthink/framework/src/think/response/Xml.php
index 9c1681a4a..bddbb48b5
--- a/thinkphp/library/think/response/Xml.php
+++ b/vendor/topthink/framework/src/think/response/Xml.php
@@ -2,19 +2,24 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\response;
use think\Collection;
+use think\Cookie;
use think\Model;
use think\Response;
+/**
+ * XML Response
+ */
class Xml extends Response
{
// 输出参数
@@ -33,13 +38,19 @@ class Xml extends Response
protected $contentType = 'text/xml';
+ public function __construct(Cookie $cookie, $data = '', int $code = 200)
+ {
+ $this->init($data, $code);
+ $this->cookie = $cookie;
+ }
+
/**
* 处理数据
* @access protected
* @param mixed $data 要处理的数据
* @return mixed
*/
- protected function output($data)
+ protected function output($data): string
{
if (is_string($data)) {
if (0 !== strpos($data, '
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route;
+
+use think\App;
+use think\Container;
+use think\Request;
+use think\Response;
+use think\Validate;
+
+/**
+ * 路由调度基础类
+ */
+abstract class Dispatch
+{
+ /**
+ * 应用对象
+ * @var \think\App
+ */
+ protected $app;
+
+ /**
+ * 请求对象
+ * @var Request
+ */
+ protected $request;
+
+ /**
+ * 路由规则
+ * @var Rule
+ */
+ protected $rule;
+
+ /**
+ * 调度信息
+ * @var mixed
+ */
+ protected $dispatch;
+
+ /**
+ * 路由变量
+ * @var array
+ */
+ protected $param;
+
+ public function __construct(Request $request, Rule $rule, $dispatch, array $param = [])
+ {
+ $this->request = $request;
+ $this->rule = $rule;
+ $this->dispatch = $dispatch;
+ $this->param = $param;
+ }
+
+ public function init(App $app)
+ {
+ $this->app = $app;
+
+ // 执行路由后置操作
+ $this->doRouteAfter();
+ }
+
+ /**
+ * 执行路由调度
+ * @access public
+ * @return mixed
+ */
+ public function run(): Response
+ {
+ if ($this->rule instanceof RuleItem && $this->request->method() == 'OPTIONS' && $this->rule->isAutoOptions()) {
+ $rules = $this->rule->getRouter()->getRule($this->rule->getRule());
+ $allow = [];
+ foreach ($rules as $item) {
+ $allow[] = strtoupper($item->getMethod());
+ }
+
+ return Response::create('', 'html', 204)->header(['Allow' => implode(', ', $allow)]);
+ }
+
+ $data = $this->exec();
+ return $this->autoResponse($data);
+ }
+
+ protected function autoResponse($data): Response
+ {
+ if ($data instanceof Response) {
+ $response = $data;
+ } elseif (!is_null($data)) {
+ // 默认自动识别响应输出类型
+ $type = $this->request->isJson() ? 'json' : 'html';
+ $response = Response::create($data, $type);
+ } else {
+ $data = ob_get_clean();
+
+ $content = false === $data ? '' : $data;
+ $status = '' === $content && $this->request->isJson() ? 204 : 200;
+ $response = Response::create($content, 'html', $status);
+ }
+
+ return $response;
+ }
+
+ /**
+ * 检查路由后置操作
+ * @access protected
+ * @return void
+ */
+ protected function doRouteAfter(): void
+ {
+ $option = $this->rule->getOption();
+
+ // 添加中间件
+ if (!empty($option['middleware'])) {
+ $this->app->middleware->import($option['middleware'], 'route');
+ }
+
+ if (!empty($option['append'])) {
+ $this->param = array_merge($this->param, $option['append']);
+ }
+
+ // 绑定模型数据
+ if (!empty($option['model'])) {
+ $this->createBindModel($option['model'], $this->param);
+ }
+
+ // 记录当前请求的路由规则
+ $this->request->setRule($this->rule);
+
+ // 记录路由变量
+ $this->request->setRoute($this->param);
+
+ // 数据自动验证
+ if (isset($option['validate'])) {
+ $this->autoValidate($option['validate']);
+ }
+ }
+
+ /**
+ * 路由绑定模型实例
+ * @access protected
+ * @param array $bindModel 绑定模型
+ * @param array $matches 路由变量
+ * @return void
+ */
+ protected function createBindModel(array $bindModel, array $matches): void
+ {
+ foreach ($bindModel as $key => $val) {
+ if ($val instanceof \Closure) {
+ $result = $this->app->invokeFunction($val, $matches);
+ } else {
+ $fields = explode('&', $key);
+
+ if (is_array($val)) {
+ [$model, $exception] = $val;
+ } else {
+ $model = $val;
+ $exception = true;
+ }
+
+ $where = [];
+ $match = true;
+
+ foreach ($fields as $field) {
+ if (!isset($matches[$field])) {
+ $match = false;
+ break;
+ } else {
+ $where[] = [$field, '=', $matches[$field]];
+ }
+ }
+
+ if ($match) {
+ $result = $model::where($where)->failException($exception)->find();
+ }
+ }
+
+ if (!empty($result)) {
+ // 注入容器
+ $this->app->instance(get_class($result), $result);
+ }
+ }
+ }
+
+ /**
+ * 验证数据
+ * @access protected
+ * @param array $option
+ * @return void
+ * @throws \think\exception\ValidateException
+ */
+ protected function autoValidate(array $option): void
+ {
+ [$validate, $scene, $message, $batch] = $option;
+
+ if (is_array($validate)) {
+ // 指定验证规则
+ $v = new Validate();
+ $v->rule($validate);
+ } else {
+ // 调用验证器
+ $class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
+
+ $v = new $class();
+
+ if (!empty($scene)) {
+ $v->scene($scene);
+ }
+ }
+
+ /** @var Validate $v */
+ $v->message($message)
+ ->batch($batch)
+ ->failException(true)
+ ->check($this->request->param());
+ }
+
+ public function getDispatch()
+ {
+ return $this->dispatch;
+ }
+
+ public function getParam(): array
+ {
+ return $this->param;
+ }
+
+ abstract public function exec();
+
+ public function __sleep()
+ {
+ return ['rule', 'dispatch', 'param', 'controller', 'actionName'];
+ }
+
+ public function __wakeup()
+ {
+ $this->app = Container::pull('app');
+ $this->request = $this->app->request;
+ }
+
+ public function __debugInfo()
+ {
+ return [
+ 'dispatch' => $this->dispatch,
+ 'param' => $this->param,
+ 'rule' => $this->rule,
+ ];
+ }
+}
diff --git a/thinkphp/library/think/route/Domain.php b/vendor/topthink/framework/src/think/route/Domain.php
old mode 100755
new mode 100644
similarity index 57%
rename from thinkphp/library/think/route/Domain.php
rename to vendor/topthink/framework/src/think/route/Domain.php
index 80950dc28..84f1d463b
--- a/thinkphp/library/think/route/Domain.php
+++ b/vendor/topthink/framework/src/think/route/Domain.php
@@ -2,22 +2,25 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\route;
-use think\Container;
-use think\Loader;
+use think\helper\Str;
+use think\Request;
use think\Route;
use think\route\dispatch\Callback as CallbackDispatch;
use think\route\dispatch\Controller as ControllerDispatch;
-use think\route\dispatch\Module as ModuleDispatch;
+/**
+ * 域名路由
+ */
class Domain extends RuleGroup
{
/**
@@ -26,16 +29,12 @@ class Domain extends RuleGroup
* @param Route $router 路由对象
* @param string $name 路由域名
* @param mixed $rule 域名路由
- * @param array $option 路由参数
- * @param array $pattern 变量规则
*/
- public function __construct(Route $router, $name = '', $rule = null, $option = [], $pattern = [])
+ public function __construct(Route $router, string $name = null, $rule = null)
{
- $this->router = $router;
- $this->domain = $name;
- $this->option = $option;
- $this->rule = $rule;
- $this->pattern = $pattern;
+ $this->router = $router;
+ $this->domain = $name;
+ $this->rule = $rule;
}
/**
@@ -46,20 +45,13 @@ class Domain extends RuleGroup
* @param bool $completeMatch 路由是否完全匹配
* @return Dispatch|false
*/
- public function check($request, $url, $completeMatch = false)
+ public function check(Request $request, string $url, bool $completeMatch = false)
{
- // 检测别名路由
- $result = $this->checkRouteAlias($request, $url);
-
- if (false !== $result) {
- return $result;
- }
-
// 检测URL绑定
$result = $this->checkUrlBind($request, $url);
if (!empty($this->option['append'])) {
- $request->setRouteVars($this->option['append']);
+ $request->setRoute($this->option['append']);
unset($this->option['append']);
}
@@ -67,12 +59,6 @@ class Domain extends RuleGroup
return $result;
}
- // 添加域名中间件
- if (!empty($this->option['middleware'])) {
- Container::get('middleware')->import($this->option['middleware']);
- unset($this->option['middleware']);
- }
-
return parent::check($request, $url, $completeMatch);
}
@@ -82,28 +68,13 @@ class Domain extends RuleGroup
* @param string $bind 绑定信息
* @return $this
*/
- public function bind($bind)
+ public function bind(string $bind)
{
$this->router->bind($bind, $this->domain);
+
return $this;
}
- /**
- * 检测路由别名
- * @access private
- * @param Request $request
- * @param string $url URL地址
- * @return Dispatch|false
- */
- private function checkRouteAlias($request, $url)
- {
- $alias = strpos($url, '|') ? strstr($url, '|', true) : $url;
-
- $item = $this->router->getAlias($alias);
-
- return $item ? $item->check($request, $url) : false;
- }
-
/**
* 检测URL绑定
* @access private
@@ -111,16 +82,13 @@ class Domain extends RuleGroup
* @param string $url URL地址
* @return Dispatch|false
*/
- private function checkUrlBind($request, $url)
+ private function checkUrlBind(Request $request, string $url)
{
- $bind = $this->router->getBind($this->domain);
+ $bind = $this->router->getDomainBind($this->domain);
- if (!empty($bind)) {
+ if ($bind) {
$this->parseBindAppendParam($bind);
- // 记录绑定信息
- Container::get('app')->log('[ BIND ] ' . var_export($bind, true));
-
// 如果有URL绑定 则进行绑定检测
$type = substr($bind, 0, 1);
$bind = substr($bind, 1);
@@ -139,10 +107,10 @@ class Domain extends RuleGroup
return false;
}
- protected function parseBindAppendParam(&$bind)
+ protected function parseBindAppendParam(string &$bind): void
{
if (false !== strpos($bind, '?')) {
- list($bind, $query) = explode('?', $bind);
+ [$bind, $query] = explode('?', $bind);
parse_str($query, $vars);
$this->append($vars);
}
@@ -156,14 +124,14 @@ class Domain extends RuleGroup
* @param string $class 类名(带命名空间)
* @return CallbackDispatch
*/
- protected function bindToClass($request, $url, $class)
+ protected function bindToClass(Request $request, string $url, string $class): CallbackDispatch
{
$array = explode('|', $url, 2);
$action = !empty($array[0]) ? $array[0] : $this->router->config('default_action');
$param = [];
if (!empty($array[1])) {
- $this->parseUrlParams($request, $array[1], $param);
+ $this->parseUrlParams($array[1], $param);
}
return new CallbackDispatch($request, $this, [$class, $action], $param);
@@ -177,7 +145,7 @@ class Domain extends RuleGroup
* @param string $namespace 命名空间
* @return CallbackDispatch
*/
- protected function bindToNamespace($request, $url, $namespace)
+ protected function bindToNamespace(Request $request, string $url, string $namespace): CallbackDispatch
{
$array = explode('|', $url, 3);
$class = !empty($array[0]) ? $array[0] : $this->router->config('default_controller');
@@ -185,52 +153,31 @@ class Domain extends RuleGroup
$param = [];
if (!empty($array[2])) {
- $this->parseUrlParams($request, $array[2], $param);
+ $this->parseUrlParams($array[2], $param);
}
- return new CallbackDispatch($request, $this, [$namespace . '\\' . Loader::parseName($class, 1), $method], $param);
+ return new CallbackDispatch($request, $this, [$namespace . '\\' . Str::studly($class), $method], $param);
}
/**
- * 绑定到控制器类
+ * 绑定到控制器
* @access protected
* @param Request $request
* @param string $url URL地址
- * @param string $controller 控制器名 (支持带模块名 index/user )
+ * @param string $controller 控制器名
* @return ControllerDispatch
*/
- protected function bindToController($request, $url, $controller)
+ protected function bindToController(Request $request, string $url, string $controller): ControllerDispatch
{
$array = explode('|', $url, 2);
$action = !empty($array[0]) ? $array[0] : $this->router->config('default_action');
$param = [];
if (!empty($array[1])) {
- $this->parseUrlParams($request, $array[1], $param);
+ $this->parseUrlParams($array[1], $param);
}
return new ControllerDispatch($request, $this, $controller . '/' . $action, $param);
}
- /**
- * 绑定到模块/控制器
- * @access protected
- * @param Request $request
- * @param string $url URL地址
- * @param string $controller 控制器类名(带命名空间)
- * @return ModuleDispatch
- */
- protected function bindToModule($request, $url, $controller)
- {
- $array = explode('|', $url, 2);
- $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action');
- $param = [];
-
- if (!empty($array[1])) {
- $this->parseUrlParams($request, $array[1], $param);
- }
-
- return new ModuleDispatch($request, $this, $controller . '/' . $action, $param);
- }
-
}
diff --git a/vendor/topthink/framework/src/think/route/Resource.php b/vendor/topthink/framework/src/think/route/Resource.php
new file mode 100644
index 000000000..bb37cb6d9
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/Resource.php
@@ -0,0 +1,251 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route;
+
+use think\Route;
+
+/**
+ * 资源路由类
+ */
+class Resource extends RuleGroup
+{
+ /**
+ * 资源路由名称
+ * @var string
+ */
+ protected $resource;
+
+ /**
+ * 资源路由地址
+ * @var string
+ */
+ protected $route;
+
+ /**
+ * REST方法定义
+ * @var array
+ */
+ protected $rest = [];
+
+ /**
+ * 模型绑定
+ * @var array
+ */
+ protected $model = [];
+
+ /**
+ * 数据验证
+ * @var array
+ */
+ protected $validate = [];
+
+ /**
+ * 中间件
+ * @var array
+ */
+ protected $middleware = [];
+
+ /**
+ * 架构函数
+ * @access public
+ * @param Route $router 路由对象
+ * @param RuleGroup $parent 上级对象
+ * @param string $name 资源名称
+ * @param string $route 路由地址
+ * @param array $rest 资源定义
+ */
+ public function __construct(Route $router, RuleGroup $parent = null, string $name = '', string $route = '', array $rest = [])
+ {
+ $name = ltrim($name, '/');
+ $this->router = $router;
+ $this->parent = $parent;
+ $this->resource = $name;
+ $this->route = $route;
+ $this->name = strpos($name, '.') ? strstr($name, '.', true) : $name;
+
+ $this->setFullName();
+
+ // 资源路由默认为完整匹配
+ $this->option['complete_match'] = true;
+
+ $this->rest = $rest;
+
+ if ($this->parent) {
+ $this->domain = $this->parent->getDomain();
+ $this->parent->addRuleItem($this);
+ }
+
+ if ($router->isTest()) {
+ $this->buildResourceRule();
+ }
+ }
+
+ /**
+ * 生成资源路由规则
+ * @access protected
+ * @return void
+ */
+ protected function buildResourceRule(): void
+ {
+ $rule = $this->resource;
+ $option = $this->option;
+ $origin = $this->router->getGroup();
+ $this->router->setGroup($this);
+
+ if (strpos($rule, '.')) {
+ // 注册嵌套资源路由
+ $array = explode('.', $rule);
+ $last = array_pop($array);
+ $item = [];
+
+ foreach ($array as $val) {
+ $item[] = $val . '/<' . ($option['var'][$val] ?? $val . '_id') . '>';
+ }
+
+ $rule = implode('/', $item) . '/' . $last;
+ }
+
+ $prefix = substr($rule, strlen($this->name) + 1);
+
+ // 注册资源路由
+ foreach ($this->rest as $key => $val) {
+ if ((isset($option['only']) && !in_array($key, $option['only']))
+ || (isset($option['except']) && in_array($key, $option['except']))) {
+ continue;
+ }
+
+ if (isset($last) && strpos($val[1], '') && isset($option['var'][$last])) {
+ $val[1] = str_replace('', '<' . $option['var'][$last] . '>', $val[1]);
+ } elseif (strpos($val[1], '') && isset($option['var'][$rule])) {
+ $val[1] = str_replace('', '<' . $option['var'][$rule] . '>', $val[1]);
+ }
+
+ $ruleItem = $this->addRule(trim($prefix . $val[1], '/'), $this->route . '/' . $val[2], $val[0]);
+
+ foreach (['model', 'validate', 'middleware', 'pattern'] as $name) {
+ if (isset($this->$name[$key])) {
+ call_user_func_array([$ruleItem, $name], (array) $this->$name[$key]);
+ }
+
+ }
+ }
+
+ $this->router->setGroup($origin);
+ }
+
+ /**
+ * 设置资源允许
+ * @access public
+ * @param array $only 资源允许
+ * @return $this
+ */
+ public function only(array $only)
+ {
+ return $this->setOption('only', $only);
+ }
+
+ /**
+ * 设置资源排除
+ * @access public
+ * @param array $except 排除资源
+ * @return $this
+ */
+ public function except(array $except)
+ {
+ return $this->setOption('except', $except);
+ }
+
+ /**
+ * 设置资源路由的变量
+ * @access public
+ * @param array $vars 资源变量
+ * @return $this
+ */
+ public function vars(array $vars)
+ {
+ return $this->setOption('var', $vars);
+ }
+
+ /**
+ * 绑定资源验证
+ * @access public
+ * @param array|string $name 资源类型或者验证信息
+ * @param array|string $validate 验证信息
+ * @return $this
+ */
+ public function withValidate($name, $validate = [])
+ {
+ if (is_array($name)) {
+ $this->validate = array_merge($this->validate, $name);
+ } else {
+ $this->validate[$name] = $validate;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 绑定资源模型
+ * @access public
+ * @param array|string $name 资源类型或者模型绑定
+ * @param array|string $model 模型绑定
+ * @return $this
+ */
+ public function withModel($name, $model = [])
+ {
+ if (is_array($name)) {
+ $this->model = array_merge($this->model, $name);
+ } else {
+ $this->model[$name] = $model;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 绑定资源模型
+ * @access public
+ * @param array|string $name 资源类型或者中间件定义
+ * @param array|string $middleware 中间件定义
+ * @return $this
+ */
+ public function withMiddleware($name, $middleware = [])
+ {
+ if (is_array($name)) {
+ $this->middleware = array_merge($this->middleware, $name);
+ } else {
+ $this->middleware[$name] = $middleware;
+ }
+
+ return $this;
+ }
+
+ /**
+ * rest方法定义和修改
+ * @access public
+ * @param array|string $name 方法名称
+ * @param array|bool $resource 资源
+ * @return $this
+ */
+ public function rest($name, $resource = [])
+ {
+ if (is_array($name)) {
+ $this->rest = $resource ? $name : array_merge($this->rest, $name);
+ } else {
+ $this->rest[$name] = $resource;
+ }
+
+ return $this;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/route/Rule.php b/vendor/topthink/framework/src/think/route/Rule.php
new file mode 100644
index 000000000..31b2e0e59
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/Rule.php
@@ -0,0 +1,905 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route;
+
+use Closure;
+use think\Container;
+use think\middleware\AllowCrossDomain;
+use think\middleware\CheckRequestCache;
+use think\middleware\FormTokenCheck;
+use think\Request;
+use think\Route;
+use think\route\dispatch\Callback as CallbackDispatch;
+use think\route\dispatch\Controller as ControllerDispatch;
+
+/**
+ * 路由规则基础类
+ */
+abstract class Rule
+{
+ /**
+ * 路由标识
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * 所在域名
+ * @var string
+ */
+ protected $domain;
+
+ /**
+ * 路由对象
+ * @var Route
+ */
+ protected $router;
+
+ /**
+ * 路由所属分组
+ * @var RuleGroup
+ */
+ protected $parent;
+
+ /**
+ * 路由规则
+ * @var mixed
+ */
+ protected $rule;
+
+ /**
+ * 路由地址
+ * @var string|Closure
+ */
+ protected $route;
+
+ /**
+ * 请求类型
+ * @var string
+ */
+ protected $method;
+
+ /**
+ * 路由变量
+ * @var array
+ */
+ protected $vars = [];
+
+ /**
+ * 路由参数
+ * @var array
+ */
+ protected $option = [];
+
+ /**
+ * 路由变量规则
+ * @var array
+ */
+ protected $pattern = [];
+
+ /**
+ * 需要和分组合并的路由参数
+ * @var array
+ */
+ protected $mergeOptions = ['model', 'append', 'middleware'];
+
+ abstract public function check(Request $request, string $url, bool $completeMatch = false);
+
+ /**
+ * 设置路由参数
+ * @access public
+ * @param array $option 参数
+ * @return $this
+ */
+ public function option(array $option)
+ {
+ $this->option = array_merge($this->option, $option);
+
+ return $this;
+ }
+
+ /**
+ * 设置单个路由参数
+ * @access public
+ * @param string $name 参数名
+ * @param mixed $value 值
+ * @return $this
+ */
+ public function setOption(string $name, $value)
+ {
+ $this->option[$name] = $value;
+
+ return $this;
+ }
+
+ /**
+ * 注册变量规则
+ * @access public
+ * @param array $pattern 变量规则
+ * @return $this
+ */
+ public function pattern(array $pattern)
+ {
+ $this->pattern = array_merge($this->pattern, $pattern);
+
+ return $this;
+ }
+
+ /**
+ * 设置标识
+ * @access public
+ * @param string $name 标识名
+ * @return $this
+ */
+ public function name(string $name)
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ /**
+ * 获取路由对象
+ * @access public
+ * @return Route
+ */
+ public function getRouter(): Route
+ {
+ return $this->router;
+ }
+
+ /**
+ * 获取Name
+ * @access public
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name ?: '';
+ }
+
+ /**
+ * 获取当前路由规则
+ * @access public
+ * @return mixed
+ */
+ public function getRule()
+ {
+ return $this->rule;
+ }
+
+ /**
+ * 获取当前路由地址
+ * @access public
+ * @return mixed
+ */
+ public function getRoute()
+ {
+ return $this->route;
+ }
+
+ /**
+ * 获取当前路由的变量
+ * @access public
+ * @return array
+ */
+ public function getVars(): array
+ {
+ return $this->vars;
+ }
+
+ /**
+ * 获取Parent对象
+ * @access public
+ * @return $this|null
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * 获取路由所在域名
+ * @access public
+ * @return string
+ */
+ public function getDomain(): string
+ {
+ return $this->domain ?: $this->parent->getDomain();
+ }
+
+ /**
+ * 获取路由参数
+ * @access public
+ * @param string $name 变量名
+ * @return mixed
+ */
+ public function config(string $name = '')
+ {
+ return $this->router->config($name);
+ }
+
+ /**
+ * 获取变量规则定义
+ * @access public
+ * @param string $name 变量名
+ * @return mixed
+ */
+ public function getPattern(string $name = '')
+ {
+ $pattern = $this->pattern;
+
+ if ($this->parent) {
+ $pattern = array_merge($this->parent->getPattern(), $pattern);
+ }
+
+ if ('' === $name) {
+ return $pattern;
+ }
+
+ return $pattern[$name] ?? null;
+ }
+
+ /**
+ * 获取路由参数定义
+ * @access public
+ * @param string $name 参数名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function getOption(string $name = '', $default = null)
+ {
+ $option = $this->option;
+
+ if ($this->parent) {
+ $parentOption = $this->parent->getOption();
+
+ // 合并分组参数
+ foreach ($this->mergeOptions as $item) {
+ if (isset($parentOption[$item]) && isset($option[$item])) {
+ $option[$item] = array_merge($parentOption[$item], $option[$item]);
+ }
+ }
+
+ $option = array_merge($parentOption, $option);
+ }
+
+ if ('' === $name) {
+ return $option;
+ }
+
+ return $option[$name] ?? $default;
+ }
+
+ /**
+ * 获取当前路由的请求类型
+ * @access public
+ * @return string
+ */
+ public function getMethod(): string
+ {
+ return strtolower($this->method);
+ }
+
+ /**
+ * 设置路由请求类型
+ * @access public
+ * @param string $method 请求类型
+ * @return $this
+ */
+ public function method(string $method)
+ {
+ return $this->setOption('method', strtolower($method));
+ }
+
+ /**
+ * 检查后缀
+ * @access public
+ * @param string $ext URL后缀
+ * @return $this
+ */
+ public function ext(string $ext = '')
+ {
+ return $this->setOption('ext', $ext);
+ }
+
+ /**
+ * 检查禁止后缀
+ * @access public
+ * @param string $ext URL后缀
+ * @return $this
+ */
+ public function denyExt(string $ext = '')
+ {
+ return $this->setOption('deny_ext', $ext);
+ }
+
+ /**
+ * 检查域名
+ * @access public
+ * @param string $domain 域名
+ * @return $this
+ */
+ public function domain(string $domain)
+ {
+ $this->domain = $domain;
+ return $this->setOption('domain', $domain);
+ }
+
+ /**
+ * 设置参数过滤检查
+ * @access public
+ * @param array $filter 参数过滤
+ * @return $this
+ */
+ public function filter(array $filter)
+ {
+ $this->option['filter'] = $filter;
+
+ return $this;
+ }
+
+ /**
+ * 绑定模型
+ * @access public
+ * @param array|string|Closure $var 路由变量名 多个使用 & 分割
+ * @param string|Closure $model 绑定模型类
+ * @param bool $exception 是否抛出异常
+ * @return $this
+ */
+ public function model($var, $model = null, bool $exception = true)
+ {
+ if ($var instanceof Closure) {
+ $this->option['model'][] = $var;
+ } elseif (is_array($var)) {
+ $this->option['model'] = $var;
+ } elseif (is_null($model)) {
+ $this->option['model']['id'] = [$var, true];
+ } else {
+ $this->option['model'][$var] = [$model, $exception];
+ }
+
+ return $this;
+ }
+
+ /**
+ * 附加路由隐式参数
+ * @access public
+ * @param array $append 追加参数
+ * @return $this
+ */
+ public function append(array $append = [])
+ {
+ $this->option['append'] = $append;
+
+ return $this;
+ }
+
+ /**
+ * 绑定验证
+ * @access public
+ * @param mixed $validate 验证器类
+ * @param string $scene 验证场景
+ * @param array $message 验证提示
+ * @param bool $batch 批量验证
+ * @return $this
+ */
+ public function validate($validate, string $scene = null, array $message = [], bool $batch = false)
+ {
+ $this->option['validate'] = [$validate, $scene, $message, $batch];
+
+ return $this;
+ }
+
+ /**
+ * 指定路由中间件
+ * @access public
+ * @param string|array|Closure $middleware 中间件
+ * @param mixed $params 参数
+ * @return $this
+ */
+ public function middleware($middleware, ...$params)
+ {
+ if (empty($params) && is_array($middleware)) {
+ $this->option['middleware'] = $middleware;
+ } else {
+ foreach ((array) $middleware as $item) {
+ $this->option['middleware'][] = [$item, $params];
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 允许跨域
+ * @access public
+ * @param array $header 自定义Header
+ * @return $this
+ */
+ public function allowCrossDomain(array $header = [])
+ {
+ return $this->middleware(AllowCrossDomain::class, $header);
+ }
+
+ /**
+ * 表单令牌验证
+ * @access public
+ * @param string $token 表单令牌token名称
+ * @return $this
+ */
+ public function token(string $token = '__token__')
+ {
+ return $this->middleware(FormTokenCheck::class, $token);
+ }
+
+ /**
+ * 设置路由缓存
+ * @access public
+ * @param array|string $cache 缓存
+ * @return $this
+ */
+ public function cache($cache)
+ {
+ return $this->middleware(CheckRequestCache::class, $cache);
+ }
+
+ /**
+ * 检查URL分隔符
+ * @access public
+ * @param string $depr URL分隔符
+ * @return $this
+ */
+ public function depr(string $depr)
+ {
+ return $this->setOption('param_depr', $depr);
+ }
+
+ /**
+ * 设置需要合并的路由参数
+ * @access public
+ * @param array $option 路由参数
+ * @return $this
+ */
+ public function mergeOptions(array $option = [])
+ {
+ $this->mergeOptions = array_merge($this->mergeOptions, $option);
+ return $this;
+ }
+
+ /**
+ * 检查是否为HTTPS请求
+ * @access public
+ * @param bool $https 是否为HTTPS
+ * @return $this
+ */
+ public function https(bool $https = true)
+ {
+ return $this->setOption('https', $https);
+ }
+
+ /**
+ * 检查是否为JSON请求
+ * @access public
+ * @param bool $json 是否为JSON
+ * @return $this
+ */
+ public function json(bool $json = true)
+ {
+ return $this->setOption('json', $json);
+ }
+
+ /**
+ * 检查是否为AJAX请求
+ * @access public
+ * @param bool $ajax 是否为AJAX
+ * @return $this
+ */
+ public function ajax(bool $ajax = true)
+ {
+ return $this->setOption('ajax', $ajax);
+ }
+
+ /**
+ * 检查是否为PJAX请求
+ * @access public
+ * @param bool $pjax 是否为PJAX
+ * @return $this
+ */
+ public function pjax(bool $pjax = true)
+ {
+ return $this->setOption('pjax', $pjax);
+ }
+
+ /**
+ * 路由到一个模板地址 需要额外传入的模板变量
+ * @access public
+ * @param array $view 视图
+ * @return $this
+ */
+ public function view(array $view = [])
+ {
+ return $this->setOption('view', $view);
+ }
+
+ /**
+ * 设置路由完整匹配
+ * @access public
+ * @param bool $match 是否完整匹配
+ * @return $this
+ */
+ public function completeMatch(bool $match = true)
+ {
+ return $this->setOption('complete_match', $match);
+ }
+
+ /**
+ * 是否去除URL最后的斜线
+ * @access public
+ * @param bool $remove 是否去除最后斜线
+ * @return $this
+ */
+ public function removeSlash(bool $remove = true)
+ {
+ return $this->setOption('remove_slash', $remove);
+ }
+
+ /**
+ * 设置路由规则全局有效
+ * @access public
+ * @return $this
+ */
+ public function crossDomainRule()
+ {
+ if ($this instanceof RuleGroup) {
+ $method = '*';
+ } else {
+ $method = $this->method;
+ }
+
+ $this->router->setCrossDomainRule($this, $method);
+
+ return $this;
+ }
+
+ /**
+ * 解析匹配到的规则路由
+ * @access public
+ * @param Request $request 请求对象
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @param string $url URL地址
+ * @param array $option 路由参数
+ * @param array $matches 匹配的变量
+ * @return Dispatch
+ */
+ public function parseRule(Request $request, string $rule, $route, string $url, array $option = [], array $matches = []): Dispatch
+ {
+ if (is_string($route) && isset($option['prefix'])) {
+ // 路由地址前缀
+ $route = $option['prefix'] . $route;
+ }
+
+ // 替换路由地址中的变量
+ $extraParams = true;
+ $search = $replace = [];
+ $depr = $this->router->config('pathinfo_depr');
+ foreach ($matches as $key => $value) {
+ $search[] = '<' . $key . '>';
+ $replace[] = $value;
+
+ $search[] = ':' . $key;
+ $replace[] = $value;
+
+ if (strpos($value, $depr)) {
+ $extraParams = false;
+ }
+ }
+
+ if (is_string($route)) {
+ $route = str_replace($search, $replace, $route);
+ }
+
+ // 解析额外参数
+ if ($extraParams) {
+ $count = substr_count($rule, '/');
+ $url = array_slice(explode('|', $url), $count + 1);
+ $this->parseUrlParams(implode('|', $url), $matches);
+ }
+
+ $this->vars = $matches;
+
+ // 发起路由调度
+ return $this->dispatch($request, $route, $option);
+ }
+
+ /**
+ * 发起路由调度
+ * @access protected
+ * @param Request $request Request对象
+ * @param mixed $route 路由地址
+ * @param array $option 路由参数
+ * @return Dispatch
+ */
+ protected function dispatch(Request $request, $route, array $option): Dispatch
+ {
+ if (is_subclass_of($route, Dispatch::class)) {
+ $result = new $route($request, $this, $route, $this->vars);
+ } elseif ($route instanceof Closure) {
+ // 执行闭包
+ $result = new CallbackDispatch($request, $this, $route, $this->vars);
+ } elseif (false !== strpos($route, '@') || false !== strpos($route, '::') || false !== strpos($route, '\\')) {
+ // 路由到类的方法
+ $route = str_replace('::', '@', $route);
+ $result = $this->dispatchMethod($request, $route);
+ } else {
+ // 路由到控制器/操作
+ $result = $this->dispatchController($request, $route);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 解析URL地址为 模块/控制器/操作
+ * @access protected
+ * @param Request $request Request对象
+ * @param string $route 路由地址
+ * @return CallbackDispatch
+ */
+ protected function dispatchMethod(Request $request, string $route): CallbackDispatch
+ {
+ $path = $this->parseUrlPath($route);
+
+ $route = str_replace('/', '@', implode('/', $path));
+ $method = strpos($route, '@') ? explode('@', $route) : $route;
+
+ return new CallbackDispatch($request, $this, $method, $this->vars);
+ }
+
+ /**
+ * 解析URL地址为 模块/控制器/操作
+ * @access protected
+ * @param Request $request Request对象
+ * @param string $route 路由地址
+ * @return ControllerDispatch
+ */
+ protected function dispatchController(Request $request, string $route): ControllerDispatch
+ {
+ $path = $this->parseUrlPath($route);
+
+ $action = array_pop($path);
+ $controller = !empty($path) ? array_pop($path) : null;
+
+ // 路由到模块/控制器/操作
+ return new ControllerDispatch($request, $this, [$controller, $action], $this->vars);
+ }
+
+ /**
+ * 路由检查
+ * @access protected
+ * @param array $option 路由参数
+ * @param Request $request Request对象
+ * @return bool
+ */
+ protected function checkOption(array $option, Request $request): bool
+ {
+ // 请求类型检测
+ if (!empty($option['method'])) {
+ if (is_string($option['method']) && false === stripos($option['method'], $request->method())) {
+ return false;
+ }
+ }
+
+ // AJAX PJAX 请求检查
+ foreach (['ajax', 'pjax', 'json'] as $item) {
+ if (isset($option[$item])) {
+ $call = 'is' . $item;
+ if ($option[$item] && !$request->$call() || !$option[$item] && $request->$call()) {
+ return false;
+ }
+ }
+ }
+
+ // 伪静态后缀检测
+ if ($request->url() != '/' && ((isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|'))
+ || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')))) {
+ return false;
+ }
+
+ // 域名检查
+ if ((isset($option['domain']) && !in_array($option['domain'], [$request->host(true), $request->subDomain()]))) {
+ return false;
+ }
+
+ // HTTPS检查
+ if ((isset($option['https']) && $option['https'] && !$request->isSsl())
+ || (isset($option['https']) && !$option['https'] && $request->isSsl())) {
+ return false;
+ }
+
+ // 请求参数检查
+ if (isset($option['filter'])) {
+ foreach ($option['filter'] as $name => $value) {
+ if ($request->param($name, '', null) != $value) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 解析URL地址中的参数Request对象
+ * @access protected
+ * @param string $rule 路由规则
+ * @param array $var 变量
+ * @return void
+ */
+ protected function parseUrlParams(string $url, array &$var = []): void
+ {
+ if ($url) {
+ preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) {
+ $var[$match[1]] = strip_tags($match[2]);
+ }, $url);
+ }
+ }
+
+ /**
+ * 解析URL的pathinfo参数
+ * @access public
+ * @param string $url URL地址
+ * @return array
+ */
+ public function parseUrlPath(string $url): array
+ {
+ // 分隔符替换 确保路由定义使用统一的分隔符
+ $url = str_replace('|', '/', $url);
+ $url = trim($url, '/');
+
+ if (strpos($url, '/')) {
+ // [控制器/操作]
+ $path = explode('/', $url);
+ } else {
+ $path = [$url];
+ }
+
+ return $path;
+ }
+
+ /**
+ * 生成路由的正则规则
+ * @access protected
+ * @param string $rule 路由规则
+ * @param array $match 匹配的变量
+ * @param array $pattern 路由变量规则
+ * @param array $option 路由参数
+ * @param bool $completeMatch 路由是否完全匹配
+ * @param string $suffix 路由正则变量后缀
+ * @return string
+ */
+ protected function buildRuleRegex(string $rule, array $match, array $pattern = [], array $option = [], bool $completeMatch = false, string $suffix = ''): string
+ {
+ foreach ($match as $name) {
+ $value = $this->buildNameRegex($name, $pattern, $suffix);
+ if ($value) {
+ $origin[] = $name;
+ $replace[] = $value;
+ }
+ }
+
+ // 是否区分 / 地址访问
+ if ('/' != $rule) {
+ if (!empty($option['remove_slash'])) {
+ $rule = rtrim($rule, '/');
+ } elseif (substr($rule, -1) == '/') {
+ $rule = rtrim($rule, '/');
+ $hasSlash = true;
+ }
+ }
+
+ $regex = isset($replace) ? str_replace($origin, $replace, $rule) : $rule;
+ $regex = str_replace([')?/', ')?-'], [')/', ')-'], $regex);
+
+ if (isset($hasSlash)) {
+ $regex .= '/';
+ }
+
+ return $regex . ($completeMatch ? '$' : '');
+ }
+
+ /**
+ * 生成路由变量的正则规则
+ * @access protected
+ * @param string $name 路由变量
+ * @param array $pattern 变量规则
+ * @param string $suffix 路由正则变量后缀
+ * @return string
+ */
+ protected function buildNameRegex(string $name, array $pattern, string $suffix): string
+ {
+ $optional = '';
+ $slash = substr($name, 0, 1);
+
+ if (in_array($slash, ['/', '-'])) {
+ $prefix = $slash;
+ $name = substr($name, 1);
+ $slash = substr($name, 0, 1);
+ } else {
+ $prefix = '';
+ }
+
+ if ('<' != $slash) {
+ return '';
+ }
+
+ if (strpos($name, '?')) {
+ $name = substr($name, 1, -2);
+ $optional = '?';
+ } elseif (strpos($name, '>')) {
+ $name = substr($name, 1, -1);
+ }
+
+ if (isset($pattern[$name])) {
+ $nameRule = $pattern[$name];
+ if (0 === strpos($nameRule, '/') && '/' == substr($nameRule, -1)) {
+ $nameRule = substr($nameRule, 1, -1);
+ }
+ } else {
+ $nameRule = $this->router->config('default_route_pattern');
+ }
+
+ return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional;
+ }
+
+ /**
+ * 设置路由参数
+ * @access public
+ * @param string $method 方法名
+ * @param array $args 调用参数
+ * @return $this
+ */
+ public function __call($method, $args)
+ {
+ if (count($args) > 1) {
+ $args[0] = $args;
+ }
+ array_unshift($args, $method);
+
+ return call_user_func_array([$this, 'setOption'], $args);
+ }
+
+ public function __sleep()
+ {
+ return ['name', 'rule', 'route', 'method', 'vars', 'option', 'pattern'];
+ }
+
+ public function __wakeup()
+ {
+ $this->router = Container::pull('route');
+ }
+
+ public function __debugInfo()
+ {
+ return [
+ 'name' => $this->name,
+ 'rule' => $this->rule,
+ 'route' => $this->route,
+ 'method' => $this->method,
+ 'vars' => $this->vars,
+ 'option' => $this->option,
+ 'pattern' => $this->pattern,
+ ];
+ }
+}
diff --git a/thinkphp/library/think/route/RuleGroup.php b/vendor/topthink/framework/src/think/route/RuleGroup.php
old mode 100755
new mode 100644
similarity index 53%
rename from thinkphp/library/think/route/RuleGroup.php
rename to vendor/topthink/framework/src/think/route/RuleGroup.php
index 36be2f444..cd9ddbd1b
--- a/thinkphp/library/think/route/RuleGroup.php
+++ b/vendor/topthink/framework/src/think/route/RuleGroup.php
@@ -2,67 +2,71 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\route;
+use Closure;
use think\Container;
use think\Exception;
use think\Request;
-use think\Response;
use think\Route;
-use think\route\dispatch\Response as ResponseDispatch;
-use think\route\dispatch\Url as UrlDispatch;
+/**
+ * 路由分组类
+ */
class RuleGroup extends Rule
{
- // 分组路由(包括子分组)
- protected $rules = [
- '*' => [],
- 'get' => [],
- 'post' => [],
- 'put' => [],
- 'patch' => [],
- 'delete' => [],
- 'head' => [],
- 'options' => [],
- ];
+ /**
+ * 分组路由(包括子分组)
+ * @var array
+ */
+ protected $rules = [];
- // MISS路由
+ /**
+ * 分组路由规则
+ * @var mixed
+ */
+ protected $rule;
+
+ /**
+ * MISS路由
+ * @var RuleItem
+ */
protected $miss;
- // 自动路由
- protected $auto;
-
- // 完整名称
+ /**
+ * 完整名称
+ * @var string
+ */
protected $fullName;
- // 所在域名
- protected $domain;
+ /**
+ * 分组别名
+ * @var string
+ */
+ protected $alias;
/**
* 架构函数
* @access public
- * @param Route $router 路由对象
- * @param RuleGroup $parent 上级对象
- * @param string $name 分组名称
- * @param mixed $rule 分组路由
- * @param array $option 路由参数
- * @param array $pattern 变量规则
+ * @param Route $router 路由对象
+ * @param RuleGroup $parent 上级对象
+ * @param string $name 分组名称
+ * @param mixed $rule 分组路由
*/
- public function __construct(Route $router, RuleGroup $parent = null, $name = '', $rule = [], $option = [], $pattern = [])
+ public function __construct(Route $router, RuleGroup $parent = null, string $name = '', $rule = null)
{
- $this->router = $router;
- $this->parent = $parent;
- $this->rule = $rule;
- $this->name = trim($name, '/');
- $this->option = $option;
- $this->pattern = $pattern;
+ $this->router = $router;
+ $this->parent = $parent;
+ $this->rule = $rule;
+ $this->name = trim($name, '/');
$this->setFullName();
@@ -71,10 +75,6 @@ class RuleGroup extends Rule
$this->parent->addRuleItem($this);
}
- if (!empty($option['cross_domain'])) {
- $this->router->setCrossDomainRule($this);
- }
-
if ($router->isTest()) {
$this->lazy(false);
}
@@ -85,7 +85,7 @@ class RuleGroup extends Rule
* @access public
* @return void
*/
- protected function setFullName()
+ protected function setFullName(): void
{
if (false !== strpos($this->name, ':')) {
$this->name = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $this->name);
@@ -96,6 +96,10 @@ class RuleGroup extends Rule
} else {
$this->fullName = $this->name;
}
+
+ if ($this->name) {
+ $this->router->getRuleName()->setGroup($this->name, $this);
+ }
}
/**
@@ -103,70 +107,53 @@ class RuleGroup extends Rule
* @access public
* @return string
*/
- public function getDomain()
+ public function getDomain(): string
{
- return $this->domain;
+ return $this->domain ?: '-';
+ }
+
+ /**
+ * 获取分组别名
+ * @access public
+ * @return string
+ */
+ public function getAlias(): string
+ {
+ return $this->alias ?: '';
}
/**
* 检测分组路由
* @access public
- * @param Request $request 请求对象
- * @param string $url 访问地址
- * @param bool $completeMatch 路由是否完全匹配
+ * @param Request $request 请求对象
+ * @param string $url 访问地址
+ * @param bool $completeMatch 路由是否完全匹配
* @return Dispatch|false
*/
- public function check($request, $url, $completeMatch = false)
+ public function check(Request $request, string $url, bool $completeMatch = false)
{
- if ($dispatch = $this->checkCrossDomain($request)) {
- // 跨域OPTIONS请求
- return $dispatch;
- }
-
// 检查分组有效性
if (!$this->checkOption($this->option, $request) || !$this->checkUrl($url)) {
return false;
}
- // 检查前置行为
- if (isset($this->option['before'])) {
- if (false === $this->checkBefore($this->option['before'])) {
- return false;
- }
- unset($this->option['before']);
- }
-
// 解析分组路由
if ($this instanceof Resource) {
$this->buildResourceRule();
- } elseif ($this->rule) {
- if ($this->rule instanceof Response) {
- return new ResponseDispatch($request, $this, $this->rule);
- }
-
+ } else {
$this->parseGroupRule($this->rule);
}
// 获取当前路由规则
$method = strtolower($request->method());
- $rules = $this->getMethodRules($method);
+ $rules = $this->getRules($method);
+ $option = $this->getOption();
- if (count($rules) == 0) {
- return false;
+ if (isset($option['complete_match'])) {
+ $completeMatch = $option['complete_match'];
}
- if ($this->parent) {
- // 合并分组参数
- $this->mergeGroupOptions();
- // 合并分组变量规则
- $this->pattern = array_merge($this->parent->getPattern(), $this->pattern);
- }
-
- if (isset($this->option['complete_match'])) {
- $completeMatch = $this->option['complete_match'];
- }
-
- if (!empty($this->option['merge_rule_regex'])) {
+ if (!empty($option['merge_rule_regex'])) {
// 合并路由正则规则进行路由匹配检查
$result = $this->checkMergeRuleRegex($request, $rules, $url, $completeMatch);
@@ -177,19 +164,18 @@ class RuleGroup extends Rule
// 检查分组路由
foreach ($rules as $key => $item) {
- $result = $item->check($request, $url, $completeMatch);
+ $result = $item[1]->check($request, $url, $completeMatch);
if (false !== $result) {
return $result;
}
}
- if ($this->auto) {
- // 自动解析URL地址
- $result = new UrlDispatch($request, $this, $this->auto . '/' . $url, ['auto_search' => false]);
+ if (!empty($option['dispatcher'])) {
+ $result = $this->parseRule($request, '', $option['dispatcher'], $url, $option);
} elseif ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) {
// 未匹配所有路由的路由规则处理
- $result = $this->miss->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->mergeGroupOptions());
+ $result = $this->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->getOption());
} else {
$result = false;
}
@@ -197,24 +183,13 @@ class RuleGroup extends Rule
return $result;
}
- /**
- * 获取当前请求的路由规则(包括子分组、资源路由)
- * @access protected
- * @param string $method
- * @return array
- */
- protected function getMethodRules($method)
- {
- return array_merge($this->rules[$method], $this->rules['*']);
- }
-
/**
* 分组URL匹配检查
* @access protected
- * @param string $url
+ * @param string $url URL
* @return bool
*/
- protected function checkUrl($url)
+ protected function checkUrl(string $url): bool
{
if ($this->fullName) {
$pos = strpos($this->fullName, '<');
@@ -234,12 +209,26 @@ class RuleGroup extends Rule
}
/**
- * 延迟解析分组的路由规则
+ * 设置路由分组别名
* @access public
- * @param bool $lazy 路由是否延迟解析
+ * @param string $alias 路由分组别名
* @return $this
*/
- public function lazy($lazy = true)
+ public function alias(string $alias)
+ {
+ $this->alias = $alias;
+ $this->router->getRuleName()->setGroup($alias, $this);
+
+ return $this;
+ }
+
+ /**
+ * 延迟解析分组的路由规则
+ * @access public
+ * @param bool $lazy 路由是否延迟解析
+ * @return $this
+ */
+ public function lazy(bool $lazy = true)
{
if (!$lazy) {
$this->parseGroupRule($this->rule);
@@ -252,18 +241,21 @@ class RuleGroup extends Rule
/**
* 解析分组和域名的路由规则及绑定
* @access public
- * @param mixed $rule 路由规则
+ * @param mixed $rule 路由规则
* @return void
*/
- public function parseGroupRule($rule)
+ public function parseGroupRule($rule): void
{
+ if (is_string($rule) && is_subclass_of($rule, Dispatch::class)) {
+ $this->dispatcher($rule);
+ return;
+ }
+
$origin = $this->router->getGroup();
$this->router->setGroup($this);
if ($rule instanceof \Closure) {
Container::getInstance()->invokeFunction($rule);
- } elseif (is_array($rule)) {
- $this->addRules($rule);
} elseif (is_string($rule) && $rule) {
$this->router->bind($rule, $this->domain);
}
@@ -274,18 +266,21 @@ class RuleGroup extends Rule
/**
* 检测分组路由
* @access public
- * @param Request $request 请求对象
- * @param array $rules 路由规则
- * @param string $url 访问地址
- * @param bool $completeMatch 路由是否完全匹配
+ * @param Request $request 请求对象
+ * @param array $rules 路由规则
+ * @param string $url 访问地址
+ * @param bool $completeMatch 路由是否完全匹配
* @return Dispatch|false
*/
- protected function checkMergeRuleRegex($request, &$rules, $url, $completeMatch)
+ protected function checkMergeRuleRegex(Request $request, array &$rules, string $url, bool $completeMatch)
{
- $depr = $this->router->config('pathinfo_depr');
- $url = $depr . str_replace('|', $depr, $url);
+ $depr = $this->router->config('pathinfo_depr');
+ $url = $depr . str_replace('|', $depr, $url);
+ $regex = [];
+ $items = [];
- foreach ($rules as $key => $item) {
+ foreach ($rules as $key => $val) {
+ $item = $val[1];
if ($item instanceof RuleItem) {
$rule = $depr . str_replace('/', $depr, $item->getRule());
if ($depr == $rule && $depr != $url) {
@@ -293,7 +288,7 @@ class RuleGroup extends Rule
continue;
}
- $complete = null !== $item->getOption('complete_match') ? $item->getOption('complete_match') : $completeMatch;
+ $complete = $item->getOption('complete_match', $completeMatch);
if (false === strpos($rule, '<')) {
if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) {
@@ -329,7 +324,7 @@ class RuleGroup extends Rule
}
try {
- $result = preg_match('/^(?:' . implode('|', $regex) . ')/u', $url, $match);
+ $result = preg_match('~^(?:' . implode('|', $regex) . ')~u', $url, $match);
} catch (\Exception $e) {
throw new Exception('route pattern error');
}
@@ -338,7 +333,7 @@ class RuleGroup extends Rule
$var = [];
foreach ($match as $key => $val) {
if (is_string($key) && '' !== $val) {
- list($name, $pos) = explode('_THINK_', $key);
+ [$name, $pos] = explode('_THINK_', $key);
$var[$name] = $val;
}
@@ -375,67 +370,41 @@ class RuleGroup extends Rule
* @access public
* @return RuleItem|null
*/
- public function getMissRule()
+ public function getMissRule(): ? RuleItem
{
return $this->miss;
}
- /**
- * 获取分组的自动路由
- * @access public
- * @return string
- */
- public function getAutoRule()
- {
- return $this->auto;
- }
-
- /**
- * 注册自动路由
- * @access public
- * @param string $route 路由规则
- * @return void
- */
- public function addAutoRule($route)
- {
- $this->auto = $route;
- }
-
/**
* 注册MISS路由
* @access public
- * @param string $route 路由地址
- * @param string $method 请求类型
- * @param array $option 路由参数
+ * @param string|Closure $route 路由地址
+ * @param string $method 请求类型
* @return RuleItem
*/
- public function addMissRule($route, $method = '*', $option = [])
+ public function miss($route, string $method = '*') : RuleItem
{
// 创建路由规则实例
- $ruleItem = new RuleItem($this->router, $this, null, '', $route, strtolower($method), $option);
+ $ruleItem = new RuleItem($this->router, $this, null, '', $route, strtolower($method));
+ $ruleItem->setMiss();
$this->miss = $ruleItem;
return $ruleItem;
}
/**
- * 添加分组下的路由规则或者子分组
+ * 添加分组下的路由规则
* @access public
- * @param string $rule 路由规则
- * @param string $route 路由地址
- * @param string $method 请求类型
- * @param array $option 路由参数
- * @param array $pattern 变量规则
- * @return $this
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @param string $method 请求类型
+ * @return RuleItem
*/
- public function addRule($rule, $route, $method = '*', $option = [], $pattern = [])
+ public function addRule(string $rule, $route = null, string $method = '*'): RuleItem
{
// 读取路由标识
- if (is_array($rule)) {
- $name = $rule[0];
- $rule = $rule[1];
- } elseif (is_string($route)) {
+ if (is_string($route)) {
$name = $route;
} else {
$name = null;
@@ -443,17 +412,12 @@ class RuleGroup extends Rule
$method = strtolower($method);
- if ('/' === $rule || '' === $rule) {
- // 首页自动完整匹配
+ if ('' === $rule || '/' === $rule) {
$rule .= '$';
}
// 创建路由规则实例
- $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method, $option, $pattern);
-
- if (!empty($option['cross_domain'])) {
- $this->router->setCrossDomainRule($ruleItem, $method);
- }
+ $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method);
$this->addRuleItem($ruleItem, $method);
@@ -461,41 +425,24 @@ class RuleGroup extends Rule
}
/**
- * 批量注册路由规则
+ * 注册分组下的路由规则
* @access public
- * @param array $rules 路由规则
- * @param string $method 请求类型
- * @param array $option 路由参数
- * @param array $pattern 变量规则
- * @return void
+ * @param Rule $rule 路由规则
+ * @param string $method 请求类型
+ * @return $this
*/
- public function addRules($rules, $method = '*', $option = [], $pattern = [])
- {
- foreach ($rules as $key => $val) {
- if (is_numeric($key)) {
- $key = array_shift($val);
- }
-
- if (is_array($val)) {
- $route = array_shift($val);
- $option = $val ? array_shift($val) : [];
- $pattern = $val ? array_shift($val) : [];
- } else {
- $route = $val;
- }
-
- $this->addRule($key, $route, $method, $option, $pattern);
- }
- }
-
- public function addRuleItem($rule, $method = '*')
+ public function addRuleItem(Rule $rule, string $method = '*')
{
if (strpos($method, '|')) {
$rule->method($method);
$method = '*';
}
- $this->rules[$method][] = $rule;
+ $this->rules[] = [$method, $rule];
+
+ if ($rule instanceof RuleItem && 'options' != $method) {
+ $this->rules[] = ['options', $rule->setAutoOptions()];
+ }
return $this;
}
@@ -503,60 +450,38 @@ class RuleGroup extends Rule
/**
* 设置分组的路由前缀
* @access public
- * @param string $prefix
+ * @param string $prefix 路由前缀
* @return $this
*/
- public function prefix($prefix)
+ public function prefix(string $prefix)
{
if ($this->parent && $this->parent->getOption('prefix')) {
$prefix = $this->parent->getOption('prefix') . $prefix;
}
- return $this->option('prefix', $prefix);
- }
-
- /**
- * 设置资源允许
- * @access public
- * @param array $only
- * @return $this
- */
- public function only($only)
- {
- return $this->option('only', $only);
- }
-
- /**
- * 设置资源排除
- * @access public
- * @param array $except
- * @return $this
- */
- public function except($except)
- {
- return $this->option('except', $except);
- }
-
- /**
- * 设置资源路由的变量
- * @access public
- * @param array $vars
- * @return $this
- */
- public function vars($vars)
- {
- return $this->option('var', $vars);
+ return $this->setOption('prefix', $prefix);
}
/**
* 合并分组的路由规则正则
* @access public
- * @param bool $merge
+ * @param bool $merge 是否合并
* @return $this
*/
- public function mergeRuleRegex($merge = true)
+ public function mergeRuleRegex(bool $merge = true)
{
- return $this->option('merge_rule_regex', $merge);
+ return $this->setOption('merge_rule_regex', $merge);
+ }
+
+ /**
+ * 设置分组的Dispatch调度
+ * @access public
+ * @param string $dispatch 调度类
+ * @return $this
+ */
+ public function dispatcher(string $dispatch)
+ {
+ return $this->setOption('dispatcher', $dispatch);
}
/**
@@ -564,24 +489,26 @@ class RuleGroup extends Rule
* @access public
* @return string
*/
- public function getFullName()
+ public function getFullName(): string
{
- return $this->fullName;
+ return $this->fullName ?: '';
}
/**
* 获取分组的路由规则
* @access public
- * @param string $method
+ * @param string $method 请求类型
* @return array
*/
- public function getRules($method = '')
+ public function getRules(string $method = ''): array
{
if ('' === $method) {
return $this->rules;
}
- return isset($this->rules[strtolower($method)]) ? $this->rules[strtolower($method)] : [];
+ return array_filter($this->rules, function ($item) use ($method) {
+ return $method == $item[0] || '*' == $item[0];
+ });
}
/**
@@ -589,17 +516,8 @@ class RuleGroup extends Rule
* @access public
* @return void
*/
- public function clear()
+ public function clear(): void
{
- $this->rules = [
- '*' => [],
- 'get' => [],
- 'post' => [],
- 'put' => [],
- 'patch' => [],
- 'delete' => [],
- 'head' => [],
- 'options' => [],
- ];
+ $this->rules = [];
}
}
diff --git a/thinkphp/library/think/route/RuleItem.php b/vendor/topthink/framework/src/think/route/RuleItem.php
old mode 100755
new mode 100644
similarity index 63%
rename from thinkphp/library/think/route/RuleItem.php
rename to vendor/topthink/framework/src/think/route/RuleItem.php
index e4bddd90c..1f9aa52a1
--- a/thinkphp/library/think/route/RuleItem.php
+++ b/vendor/topthink/framework/src/think/route/RuleItem.php
@@ -2,22 +2,36 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\route;
-use think\Container;
use think\Exception;
+use think\Request;
use think\Route;
+/**
+ * 路由规则类
+ */
class RuleItem extends Rule
{
- protected $hasSetRule;
+ /**
+ * 是否为MISS规则
+ * @var bool
+ */
+ protected $miss = false;
+
+ /**
+ * 是否为额外自动注册的OPTIONS规则
+ * @var bool
+ */
+ protected $autoOption = false;
/**
* 架构函数
@@ -25,27 +39,81 @@ class RuleItem extends Rule
* @param Route $router 路由实例
* @param RuleGroup $parent 上级对象
* @param string $name 路由标识
- * @param string|array $rule 路由规则
+ * @param string $rule 路由规则
* @param string|\Closure $route 路由地址
* @param string $method 请求类型
- * @param array $option 路由参数
- * @param array $pattern 变量规则
*/
- public function __construct(Route $router, RuleGroup $parent, $name, $rule, $route, $method = '*', $option = [], $pattern = [])
+ public function __construct(Route $router, RuleGroup $parent, string $name = null, string $rule = '', $route = null, string $method = '*')
{
- $this->router = $router;
- $this->parent = $parent;
- $this->name = $name;
- $this->route = $route;
- $this->method = $method;
- $this->option = $option;
- $this->pattern = $pattern;
+ $this->router = $router;
+ $this->parent = $parent;
+ $this->name = $name;
+ $this->route = $route;
+ $this->method = $method;
$this->setRule($rule);
- if (!empty($option['cross_domain'])) {
- $this->router->setCrossDomainRule($this, $method);
+ $this->router->setRule($this->rule, $this);
+ }
+
+ /**
+ * 设置当前路由规则为MISS路由
+ * @access public
+ * @return $this
+ */
+ public function setMiss()
+ {
+ $this->miss = true;
+ return $this;
+ }
+
+ /**
+ * 判断当前路由规则是否为MISS路由
+ * @access public
+ * @return bool
+ */
+ public function isMiss(): bool
+ {
+ return $this->miss;
+ }
+
+ /**
+ * 设置当前路由为自动注册OPTIONS
+ * @access public
+ * @return $this
+ */
+ public function setAutoOptions()
+ {
+ $this->autoOption = true;
+ return $this;
+ }
+
+ /**
+ * 判断当前路由规则是否为自动注册的OPTIONS路由
+ * @access public
+ * @return bool
+ */
+ public function isAutoOptions(): bool
+ {
+ return $this->autoOption;
+ }
+
+ /**
+ * 获取当前路由的URL后缀
+ * @access public
+ * @return string|null
+ */
+ public function getSuffix()
+ {
+ if (isset($this->option['ext'])) {
+ $suffix = $this->option['ext'];
+ } elseif ($this->parent->getOption('ext')) {
+ $suffix = $this->parent->getOption('ext');
+ } else {
+ $suffix = null;
}
+
+ return $suffix;
}
/**
@@ -54,7 +122,7 @@ class RuleItem extends Rule
* @param string $rule 路由规则
* @return void
*/
- public function setRule($rule)
+ public function setRule(string $rule): void
{
if ('$' == substr($rule, -1, 1)) {
// 是否完整匹配
@@ -79,27 +147,13 @@ class RuleItem extends Rule
$this->setRuleName();
}
- /**
- * 检查后缀
- * @access public
- * @param string $ext
- * @return $this
- */
- public function ext($ext = '')
- {
- $this->option('ext', $ext);
- $this->setRuleName(true);
-
- return $this;
- }
-
/**
* 设置别名
* @access public
* @param string $name
* @return $this
*/
- public function name($name)
+ public function name(string $name)
{
$this->name = $name;
$this->setRuleName(true);
@@ -110,31 +164,13 @@ class RuleItem extends Rule
/**
* 设置路由标识 用于URL反解生成
* @access protected
- * @param bool $first 是否插入开头
+ * @param bool $first 是否插入开头
* @return void
*/
- protected function setRuleName($first = false)
+ protected function setRuleName(bool $first = false): void
{
if ($this->name) {
- $vars = $this->parseVar($this->rule);
- $name = strtolower($this->name);
-
- if (isset($this->option['ext'])) {
- $suffix = $this->option['ext'];
- } elseif ($this->parent->getOption('ext')) {
- $suffix = $this->parent->getOption('ext');
- } else {
- $suffix = null;
- }
-
- $value = [$this->rule, $vars, $this->parent->getDomain(), $suffix, $this->method];
-
- Container::get('rule_name')->set($name, $value, $first);
- }
-
- if (!$this->hasSetRule) {
- Container::get('rule_name')->setRule($this->rule, $this);
- $this->hasSetRule = true;
+ $this->router->setName($this->name, $this, $first);
}
}
@@ -147,33 +183,23 @@ class RuleItem extends Rule
* @param bool $completeMatch 路由是否完全匹配
* @return Dispatch|false
*/
- public function checkRule($request, $url, $match = null, $completeMatch = false)
+ public function checkRule(Request $request, string $url, $match = null, bool $completeMatch = false)
{
- if ($dispatch = $this->checkCrossDomain($request)) {
- // 允许跨域
- return $dispatch;
- }
-
// 检查参数有效性
if (!$this->checkOption($this->option, $request)) {
return false;
}
// 合并分组参数
- $option = $this->mergeGroupOptions();
-
- $url = $this->urlSuffixCheck($request, $url, $option);
+ $option = $this->getOption();
+ $pattern = $this->getPattern();
+ $url = $this->urlSuffixCheck($request, $url, $option);
if (is_null($match)) {
- $match = $this->match($url, $option, $completeMatch);
+ $match = $this->match($url, $option, $pattern, $completeMatch);
}
if (false !== $match) {
- // 检查前置行为
- if (isset($option['before']) && false === $this->checkBefore($option['before'])) {
- return false;
- }
-
return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match);
}
@@ -185,11 +211,10 @@ class RuleItem extends Rule
* @access public
* @param Request $request 请求对象
* @param string $url 访问地址
- * @param string $depr 路径分隔符
* @param bool $completeMatch 路由是否完全匹配
* @return Dispatch|false
*/
- public function check($request, $url, $completeMatch = false)
+ public function check(Request $request, string $url, bool $completeMatch = false)
{
return $this->checkRule($request, $url, null, $completeMatch);
}
@@ -202,7 +227,7 @@ class RuleItem extends Rule
* @param array $option 路由参数
* @return string
*/
- protected function urlSuffixCheck($request, $url, $option = [])
+ protected function urlSuffixCheck(Request $request, string $url, array $option = []): string
{
// 是否区分 / 地址访问
if (!empty($option['remove_slash']) && '/' != $this->rule) {
@@ -223,20 +248,20 @@ class RuleItem extends Rule
* @access private
* @param string $url URL地址
* @param array $option 路由参数
- * @param bool $completeMatch 路由是否完全匹配
+ * @param array $pattern 变量规则
+ * @param bool $completeMatch 是否完全匹配
* @return array|false
*/
- private function match($url, $option, $completeMatch)
+ private function match(string $url, array $option, array $pattern, bool $completeMatch)
{
if (isset($option['complete_match'])) {
$completeMatch = $option['complete_match'];
}
- $pattern = array_merge($this->parent->getPattern(), $this->pattern);
- $depr = $this->router->config('pathinfo_depr');
+ $depr = $this->router->config('pathinfo_depr');
// 检查完整规则定义
- if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) {
+ if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . ($completeMatch ? '$' : '') . '/', str_replace('|', $depr, $url))) {
return false;
}
@@ -267,7 +292,7 @@ class RuleItem extends Rule
$regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch);
try {
- if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/u', $url, $match)) {
+ if (!preg_match('~^' . $regex . '~u', $url, $match)) {
return false;
}
} catch (\Exception $e) {
@@ -285,4 +310,21 @@ class RuleItem extends Rule
return $var;
}
+ /**
+ * 设置路由所属分组(用于注解路由)
+ * @access public
+ * @param string $name 分组名称或者标识
+ * @return $this
+ */
+ public function group(string $name)
+ {
+ $group = $this->router->getRuleName()->getGroup($name);
+
+ if ($group) {
+ $this->parent = $group;
+ $this->setRule($this->rule);
+ }
+
+ return $this;
+ }
}
diff --git a/vendor/topthink/framework/src/think/route/RuleName.php b/vendor/topthink/framework/src/think/route/RuleName.php
new file mode 100644
index 000000000..0684367cd
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/RuleName.php
@@ -0,0 +1,211 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route;
+
+/**
+ * 路由标识管理类
+ */
+class RuleName
+{
+ /**
+ * 路由标识
+ * @var array
+ */
+ protected $item = [];
+
+ /**
+ * 路由规则
+ * @var array
+ */
+ protected $rule = [];
+
+ /**
+ * 路由分组
+ * @var array
+ */
+ protected $group = [];
+
+ /**
+ * 注册路由标识
+ * @access public
+ * @param string $name 路由标识
+ * @param RuleItem $ruleItem 路由规则
+ * @param bool $first 是否优先
+ * @return void
+ */
+ public function setName(string $name, RuleItem $ruleItem, bool $first = false): void
+ {
+ $name = strtolower($name);
+ $item = $this->getRuleItemInfo($ruleItem);
+ if ($first && isset($this->item[$name])) {
+ array_unshift($this->item[$name], $item);
+ } else {
+ $this->item[$name][] = $item;
+ }
+ }
+
+ /**
+ * 注册路由分组标识
+ * @access public
+ * @param string $name 路由分组标识
+ * @param RuleGroup $group 路由分组
+ * @return void
+ */
+ public function setGroup(string $name, RuleGroup $group): void
+ {
+ $this->group[strtolower($name)] = $group;
+ }
+
+ /**
+ * 注册路由规则
+ * @access public
+ * @param string $rule 路由规则
+ * @param RuleItem $ruleItem 路由
+ * @return void
+ */
+ public function setRule(string $rule, RuleItem $ruleItem): void
+ {
+ $route = $ruleItem->getRoute();
+
+ if (is_string($route)) {
+ $this->rule[$rule][$route] = $ruleItem;
+ } else {
+ $this->rule[$rule][] = $ruleItem;
+ }
+ }
+
+ /**
+ * 根据路由规则获取路由对象(列表)
+ * @access public
+ * @param string $rule 路由标识
+ * @return RuleItem[]
+ */
+ public function getRule(string $rule): array
+ {
+ return $this->rule[$rule] ?? [];
+ }
+
+ /**
+ * 根据路由分组标识获取分组
+ * @access public
+ * @param string $name 路由分组标识
+ * @return RuleGroup|null
+ */
+ public function getGroup(string $name)
+ {
+ return $this->group[strtolower($name)] ?? null;
+ }
+
+ /**
+ * 清空路由规则
+ * @access public
+ * @return void
+ */
+ public function clear(): void
+ {
+ $this->item = [];
+ $this->rule = [];
+ }
+
+ /**
+ * 获取全部路由列表
+ * @access public
+ * @return array
+ */
+ public function getRuleList(): array
+ {
+ $list = [];
+
+ foreach ($this->rule as $rule => $rules) {
+ foreach ($rules as $item) {
+ $val = [];
+
+ foreach (['method', 'rule', 'name', 'route', 'domain', 'pattern', 'option'] as $param) {
+ $call = 'get' . $param;
+ $val[$param] = $item->$call();
+ }
+
+ if ($item->isMiss()) {
+ $val['rule'] .= '';
+ }
+
+ $list[] = $val;
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * 导入路由标识
+ * @access public
+ * @param array $item 路由标识
+ * @return void
+ */
+ public function import(array $item): void
+ {
+ $this->item = $item;
+ }
+
+ /**
+ * 根据路由标识获取路由信息(用于URL生成)
+ * @access public
+ * @param string $name 路由标识
+ * @param string $domain 域名
+ * @param string $method 请求类型
+ * @return array
+ */
+ public function getName(string $name = null, string $domain = null, string $method = '*'): array
+ {
+ if (is_null($name)) {
+ return $this->item;
+ }
+
+ $name = strtolower($name);
+ $method = strtolower($method);
+ $result = [];
+
+ if (isset($this->item[$name])) {
+ if (is_null($domain)) {
+ $result = $this->item[$name];
+ } else {
+ foreach ($this->item[$name] as $item) {
+ $itemDomain = $item['domain'];
+ $itemMethod = $item['method'];
+
+ if (($itemDomain == $domain || '-' == $itemDomain) && ('*' == $itemMethod || '*' == $method || $method == $itemMethod)) {
+ $result[] = $item;
+ }
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 获取路由信息
+ * @access protected
+ * @param RuleItem $item 路由规则
+ * @return array
+ */
+ protected function getRuleItemInfo(RuleItem $item): array
+ {
+ return [
+ 'rule' => $item->getRule(),
+ 'domain' => $item->getDomain(),
+ 'method' => $item->getMethod(),
+ 'suffix' => $item->getSuffix(),
+ ];
+ }
+}
diff --git a/vendor/topthink/framework/src/think/route/Url.php b/vendor/topthink/framework/src/think/route/Url.php
new file mode 100644
index 000000000..8dd410cbe
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/Url.php
@@ -0,0 +1,517 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route;
+
+use think\App;
+use think\Route;
+
+/**
+ * 路由地址生成
+ */
+class Url
+{
+ /**
+ * 应用对象
+ * @var App
+ */
+ protected $app;
+
+ /**
+ * 路由对象
+ * @var Route
+ */
+ protected $route;
+
+ /**
+ * URL变量
+ * @var array
+ */
+ protected $vars = [];
+
+ /**
+ * 路由URL
+ * @var string
+ */
+ protected $url;
+
+ /**
+ * URL 根地址
+ * @var string
+ */
+ protected $root = '';
+
+ /**
+ * HTTPS
+ * @var bool
+ */
+ protected $https;
+
+ /**
+ * URL后缀
+ * @var string|bool
+ */
+ protected $suffix = true;
+
+ /**
+ * URL域名
+ * @var string|bool
+ */
+ protected $domain = false;
+
+ /**
+ * 架构函数
+ * @access public
+ * @param string $url URL地址
+ * @param array $vars 参数
+ */
+ public function __construct(Route $route, App $app, string $url = '', array $vars = [])
+ {
+ $this->route = $route;
+ $this->app = $app;
+ $this->url = $url;
+ $this->vars = $vars;
+ }
+
+ /**
+ * 设置URL参数
+ * @access public
+ * @param array $vars URL参数
+ * @return $this
+ */
+ public function vars(array $vars = [])
+ {
+ $this->vars = $vars;
+ return $this;
+ }
+
+ /**
+ * 设置URL后缀
+ * @access public
+ * @param string|bool $suffix URL后缀
+ * @return $this
+ */
+ public function suffix($suffix)
+ {
+ $this->suffix = $suffix;
+ return $this;
+ }
+
+ /**
+ * 设置URL域名(或者子域名)
+ * @access public
+ * @param string|bool $domain URL域名
+ * @return $this
+ */
+ public function domain($domain)
+ {
+ $this->domain = $domain;
+ return $this;
+ }
+
+ /**
+ * 设置URL 根地址
+ * @access public
+ * @param string $root URL root
+ * @return $this
+ */
+ public function root(string $root)
+ {
+ $this->root = $root;
+ return $this;
+ }
+
+ /**
+ * 设置是否使用HTTPS
+ * @access public
+ * @param bool $https
+ * @return $this
+ */
+ public function https(bool $https = true)
+ {
+ $this->https = $https;
+ return $this;
+ }
+
+ /**
+ * 检测域名
+ * @access protected
+ * @param string $url URL
+ * @param string|true $domain 域名
+ * @return string
+ */
+ protected function parseDomain(string &$url, $domain): string
+ {
+ if (!$domain) {
+ return '';
+ }
+
+ $request = $this->app->request;
+ $rootDomain = $request->rootDomain();
+
+ if (true === $domain) {
+ // 自动判断域名
+ $domain = $request->host();
+ $domains = $this->route->getDomains();
+
+ if (!empty($domains)) {
+ $routeDomain = array_keys($domains);
+ foreach ($routeDomain as $domainPrefix) {
+ if (0 === strpos($domainPrefix, '*.') && strpos($domain, ltrim($domainPrefix, '*.')) !== false) {
+ foreach ($domains as $key => $rule) {
+ $rule = is_array($rule) ? $rule[0] : $rule;
+ if (is_string($rule) && false === strpos($key, '*') && 0 === strpos($url, $rule)) {
+ $url = ltrim($url, $rule);
+ $domain = $key;
+
+ // 生成对应子域名
+ if (!empty($rootDomain)) {
+ $domain .= $rootDomain;
+ }
+ break;
+ } elseif (false !== strpos($key, '*')) {
+ if (!empty($rootDomain)) {
+ $domain .= $rootDomain;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+ } elseif (false === strpos($domain, '.') && 0 !== strpos($domain, $rootDomain)) {
+ $domain .= '.' . $rootDomain;
+ }
+
+ if (false !== strpos($domain, '://')) {
+ $scheme = '';
+ } else {
+ $scheme = $this->https || $request->isSsl() ? 'https://' : 'http://';
+ }
+
+ return $scheme . $domain;
+ }
+
+ /**
+ * 解析URL后缀
+ * @access protected
+ * @param string|bool $suffix 后缀
+ * @return string
+ */
+ protected function parseSuffix($suffix): string
+ {
+ if ($suffix) {
+ $suffix = true === $suffix ? $this->route->config('url_html_suffix') : $suffix;
+
+ if (is_string($suffix) && $pos = strpos($suffix, '|')) {
+ $suffix = substr($suffix, 0, $pos);
+ }
+ }
+
+ return (empty($suffix) || 0 === strpos($suffix, '.')) ? (string) $suffix : '.' . $suffix;
+ }
+
+ /**
+ * 直接解析URL地址
+ * @access protected
+ * @param string $url URL
+ * @param string|bool $domain Domain
+ * @return string
+ */
+ protected function parseUrl(string $url, &$domain): string
+ {
+ $request = $this->app->request;
+
+ if (0 === strpos($url, '/')) {
+ // 直接作为路由地址解析
+ $url = substr($url, 1);
+ } elseif (false !== strpos($url, '\\')) {
+ // 解析到类
+ $url = ltrim(str_replace('\\', '/', $url), '/');
+ } elseif (0 === strpos($url, '@')) {
+ // 解析到控制器
+ $url = substr($url, 1);
+ } elseif ('' === $url) {
+ $url = $request->controller() . '/' . $request->action();
+ } else {
+ $controller = $request->controller();
+
+ $path = explode('/', $url);
+ $action = array_pop($path);
+ $controller = empty($path) ? $controller : array_pop($path);
+
+ $url = $controller . '/' . $action;
+ }
+
+ return $url;
+ }
+
+ /**
+ * 分析路由规则中的变量
+ * @access protected
+ * @param string $rule 路由规则
+ * @return array
+ */
+ protected function parseVar(string $rule): array
+ {
+ // 提取路由规则中的变量
+ $var = [];
+
+ if (preg_match_all('/<\w+\??>/', $rule, $matches)) {
+ foreach ($matches[0] as $name) {
+ $optional = false;
+
+ if (strpos($name, '?')) {
+ $name = substr($name, 1, -2);
+ $optional = true;
+ } else {
+ $name = substr($name, 1, -1);
+ }
+
+ $var[$name] = $optional ? 2 : 1;
+ }
+ }
+
+ return $var;
+ }
+
+ /**
+ * 匹配路由地址
+ * @access protected
+ * @param array $rule 路由规则
+ * @param array $vars 路由变量
+ * @param mixed $allowDomain 允许域名
+ * @return array
+ */
+ protected function getRuleUrl(array $rule, array &$vars = [], $allowDomain = ''): array
+ {
+ $request = $this->app->request;
+ if (is_string($allowDomain) && false === strpos($allowDomain, '.')) {
+ $allowDomain .= '.' . $request->rootDomain();
+ }
+ $port = $request->port();
+
+ foreach ($rule as $item) {
+ $url = $item['rule'];
+ $pattern = $this->parseVar($url);
+ $domain = $item['domain'];
+ $suffix = $item['suffix'];
+
+ if ('-' == $domain) {
+ $domain = is_string($allowDomain) ? $allowDomain : $request->host(true);
+ }
+
+ if (is_string($allowDomain) && $domain != $allowDomain) {
+ continue;
+ }
+
+ if ($port && !in_array($port, [80, 443])) {
+ $domain .= ':' . $port;
+ }
+
+ if (empty($pattern)) {
+ return [rtrim($url, '?/-'), $domain, $suffix];
+ }
+
+ $type = $this->route->config('url_common_param');
+ $keys = [];
+
+ foreach ($pattern as $key => $val) {
+ if (isset($vars[$key])) {
+ $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? (string) $vars[$key] : urlencode((string) $vars[$key]), $url);
+ $keys[] = $key;
+ $url = str_replace(['/?', '-?'], ['/', '-'], $url);
+ $result = [rtrim($url, '?/-'), $domain, $suffix];
+ } elseif (2 == $val) {
+ $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url);
+ $url = str_replace(['/?', '-?'], ['/', '-'], $url);
+ $result = [rtrim($url, '?/-'), $domain, $suffix];
+ } else {
+ $result = null;
+ $keys = [];
+ break;
+ }
+ }
+
+ $vars = array_diff_key($vars, array_flip($keys));
+
+ if (isset($result)) {
+ return $result;
+ }
+ }
+
+ return [];
+ }
+
+ /**
+ * 生成URL地址
+ * @access public
+ * @return string
+ */
+ public function build()
+ {
+ // 解析URL
+ $url = $this->url;
+ $suffix = $this->suffix;
+ $domain = $this->domain;
+ $request = $this->app->request;
+ $vars = $this->vars;
+
+ if (0 === strpos($url, '[') && $pos = strpos($url, ']')) {
+ // [name] 表示使用路由命名标识生成URL
+ $name = substr($url, 1, $pos - 1);
+ $url = 'name' . substr($url, $pos + 1);
+ }
+
+ if (false === strpos($url, '://') && 0 !== strpos($url, '/')) {
+ $info = parse_url($url);
+ $url = !empty($info['path']) ? $info['path'] : '';
+
+ if (isset($info['fragment'])) {
+ // 解析锚点
+ $anchor = $info['fragment'];
+
+ if (false !== strpos($anchor, '?')) {
+ // 解析参数
+ [$anchor, $info['query']] = explode('?', $anchor, 2);
+ }
+
+ if (false !== strpos($anchor, '@')) {
+ // 解析域名
+ [$anchor, $domain] = explode('@', $anchor, 2);
+ }
+ } elseif (strpos($url, '@') && false === strpos($url, '\\')) {
+ // 解析域名
+ [$url, $domain] = explode('@', $url, 2);
+ }
+ }
+
+ if ($url) {
+ $checkName = isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '');
+ $checkDomain = $domain && is_string($domain) ? $domain : null;
+
+ $rule = $this->route->getName($checkName, $checkDomain);
+
+ if (empty($rule) && isset($info['query'])) {
+ $rule = $this->route->getName($url, $checkDomain);
+ // 解析地址里面参数 合并到vars
+ parse_str($info['query'], $params);
+ $vars = array_merge($params, $vars);
+ unset($info['query']);
+ }
+ }
+
+ if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars, $domain)) {
+ // 匹配路由命名标识
+ $url = $match[0];
+
+ if ($domain && !empty($match[1])) {
+ $domain = $match[1];
+ }
+
+ if (!is_null($match[2])) {
+ $suffix = $match[2];
+ }
+ } elseif (!empty($rule) && isset($name)) {
+ throw new \InvalidArgumentException('route name not exists:' . $name);
+ } else {
+ // 检测URL绑定
+ $bind = $this->route->getDomainBind($domain && is_string($domain) ? $domain : null);
+
+ if ($bind && 0 === strpos($url, $bind)) {
+ $url = substr($url, strlen($bind) + 1);
+ } else {
+ $binds = $this->route->getBind();
+
+ foreach ($binds as $key => $val) {
+ if (is_string($val) && 0 === strpos($url, $val) && substr_count($val, '/') > 1) {
+ $url = substr($url, strlen($val) + 1);
+ $domain = $key;
+ break;
+ }
+ }
+ }
+
+ // 路由标识不存在 直接解析
+ $url = $this->parseUrl($url, $domain);
+
+ if (isset($info['query'])) {
+ // 解析地址里面参数 合并到vars
+ parse_str($info['query'], $params);
+ $vars = array_merge($params, $vars);
+ }
+ }
+
+ // 还原URL分隔符
+ $depr = $this->route->config('pathinfo_depr');
+ $url = str_replace('/', $depr, $url);
+
+ $file = $request->baseFile();
+ if ($file && 0 !== strpos($request->url(), $file)) {
+ $file = str_replace('\\', '/', dirname($file));
+ }
+
+ $url = rtrim($file, '/') . '/' . $url;
+
+ // URL后缀
+ if ('/' == substr($url, -1) || '' == $url) {
+ $suffix = '';
+ } else {
+ $suffix = $this->parseSuffix($suffix);
+ }
+
+ // 锚点
+ $anchor = !empty($anchor) ? '#' . $anchor : '';
+
+ // 参数组装
+ if (!empty($vars)) {
+ // 添加参数
+ if ($this->route->config('url_common_param')) {
+ $vars = http_build_query($vars);
+ $url .= $suffix . ($vars ? '?' . $vars : '') . $anchor;
+ } else {
+ foreach ($vars as $var => $val) {
+ $val = (string) $val;
+ if ('' !== $val) {
+ $url .= $depr . $var . $depr . urlencode($val);
+ }
+ }
+
+ $url .= $suffix . $anchor;
+ }
+ } else {
+ $url .= $suffix . $anchor;
+ }
+
+ // 检测域名
+ $domain = $this->parseDomain($url, $domain);
+
+ // URL组装
+ return $domain . rtrim($this->root, '/') . '/' . ltrim($url, '/');
+ }
+
+ public function __toString()
+ {
+ return $this->build();
+ }
+
+ public function __debugInfo()
+ {
+ return [
+ 'url' => $this->url,
+ 'vars' => $this->vars,
+ 'suffix' => $this->suffix,
+ 'domain' => $this->domain,
+ ];
+ }
+}
diff --git a/thinkphp/library/think/route/dispatch/Callback.php b/vendor/topthink/framework/src/think/route/dispatch/Callback.php
old mode 100755
new mode 100644
similarity index 87%
rename from thinkphp/library/think/route/dispatch/Callback.php
rename to vendor/topthink/framework/src/think/route/dispatch/Callback.php
index ca76fc993..2044ef8e7
--- a/thinkphp/library/think/route/dispatch/Callback.php
+++ b/vendor/topthink/framework/src/think/route/dispatch/Callback.php
@@ -2,17 +2,21 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\route\dispatch;
use think\route\Dispatch;
+/**
+ * Callback Dispatcher
+ */
class Callback extends Dispatch
{
public function exec()
diff --git a/vendor/topthink/framework/src/think/route/dispatch/Controller.php b/vendor/topthink/framework/src/think/route/dispatch/Controller.php
new file mode 100644
index 000000000..611101bde
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/dispatch/Controller.php
@@ -0,0 +1,183 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route\dispatch;
+
+use ReflectionClass;
+use ReflectionException;
+use ReflectionMethod;
+use think\App;
+use think\exception\ClassNotFoundException;
+use think\exception\HttpException;
+use think\helper\Str;
+use think\route\Dispatch;
+
+/**
+ * Controller Dispatcher
+ */
+class Controller extends Dispatch
+{
+ /**
+ * 控制器名
+ * @var string
+ */
+ protected $controller;
+
+ /**
+ * 操作名
+ * @var string
+ */
+ protected $actionName;
+
+ public function init(App $app)
+ {
+ parent::init($app);
+
+ $result = $this->dispatch;
+
+ if (is_string($result)) {
+ $result = explode('/', $result);
+ }
+
+ // 获取控制器名
+ $controller = strip_tags($result[0] ?: $this->rule->config('default_controller'));
+
+ if (strpos($controller, '.')) {
+ $pos = strrpos($controller, '.');
+ $this->controller = substr($controller, 0, $pos) . '.' . Str::studly(substr($controller, $pos + 1));
+ } else {
+ $this->controller = Str::studly($controller);
+ }
+
+ // 获取操作名
+ $this->actionName = strip_tags($result[1] ?: $this->rule->config('default_action'));
+
+ // 设置当前请求的控制器、操作
+ $this->request
+ ->setController($this->controller)
+ ->setAction($this->actionName);
+ }
+
+ public function exec()
+ {
+ try {
+ // 实例化控制器
+ $instance = $this->controller($this->controller);
+ } catch (ClassNotFoundException $e) {
+ throw new HttpException(404, 'controller not exists:' . $e->getClass());
+ }
+
+ // 注册控制器中间件
+ $this->registerControllerMiddleware($instance);
+
+ return $this->app->middleware->pipeline('controller')
+ ->send($this->request)
+ ->then(function () use ($instance) {
+ // 获取当前操作名
+ $suffix = $this->rule->config('action_suffix');
+ $action = $this->actionName . $suffix;
+
+ if (is_callable([$instance, $action])) {
+ $vars = $this->request->param();
+ try {
+ $reflect = new ReflectionMethod($instance, $action);
+ // 严格获取当前操作方法名
+ $actionName = $reflect->getName();
+ if ($suffix) {
+ $actionName = substr($actionName, 0, -strlen($suffix));
+ }
+
+ $this->request->setAction($actionName);
+ } catch (ReflectionException $e) {
+ $reflect = new ReflectionMethod($instance, '__call');
+ $vars = [$action, $vars];
+ $this->request->setAction($action);
+ }
+ } else {
+ // 操作不存在
+ throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
+ }
+
+ $data = $this->app->invokeReflectMethod($instance, $reflect, $vars);
+
+ return $this->autoResponse($data);
+ });
+ }
+
+ /**
+ * 使用反射机制注册控制器中间件
+ * @access public
+ * @param object $controller 控制器实例
+ * @return void
+ */
+ protected function registerControllerMiddleware($controller): void
+ {
+ $class = new ReflectionClass($controller);
+
+ if ($class->hasProperty('middleware')) {
+ $reflectionProperty = $class->getProperty('middleware');
+ $reflectionProperty->setAccessible(true);
+
+ $middlewares = $reflectionProperty->getValue($controller);
+
+ foreach ($middlewares as $key => $val) {
+ if (!is_int($key)) {
+ if (isset($val['only']) && !in_array($this->request->action(true), array_map(function ($item) {
+ return strtolower($item);
+ }, is_string($val['only']) ? explode(",", $val['only']) : $val['only']))) {
+ continue;
+ } elseif (isset($val['except']) && in_array($this->request->action(true), array_map(function ($item) {
+ return strtolower($item);
+ }, is_string($val['except']) ? explode(',', $val['except']) : $val['except']))) {
+ continue;
+ } else {
+ $val = $key;
+ }
+ }
+
+ if (is_string($val) && strpos($val, ':')) {
+ $val = explode(':', $val);
+ if (count($val) > 1) {
+ $val = [$val[0], array_slice($val, 1)];
+ }
+ }
+
+ $this->app->middleware->controller($val);
+ }
+ }
+ }
+
+ /**
+ * 实例化访问控制器
+ * @access public
+ * @param string $name 资源地址
+ * @return object
+ * @throws ClassNotFoundException
+ */
+ public function controller(string $name)
+ {
+ $suffix = $this->rule->config('controller_suffix') ? 'Controller' : '';
+
+ $controllerLayer = $this->rule->config('controller_layer') ?: 'controller';
+ $emptyController = $this->rule->config('empty_controller') ?: 'Error';
+
+ $class = $this->app->parseClass($controllerLayer, $name . $suffix);
+
+ if (class_exists($class)) {
+ return $this->app->make($class, [], true);
+ } elseif ($emptyController && class_exists($emptyClass = $this->app->parseClass($controllerLayer, $emptyController . $suffix))) {
+ return $this->app->make($emptyClass, [], true);
+ }
+
+ throw new ClassNotFoundException('class not exists:' . $class, $class);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/route/dispatch/Url.php b/vendor/topthink/framework/src/think/route/dispatch/Url.php
new file mode 100644
index 000000000..147f5cb75
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/dispatch/Url.php
@@ -0,0 +1,118 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route\dispatch;
+
+use think\exception\HttpException;
+use think\helper\Str;
+use think\Request;
+use think\route\Rule;
+
+/**
+ * Url Dispatcher
+ */
+class Url extends Controller
+{
+
+ public function __construct(Request $request, Rule $rule, $dispatch)
+ {
+ $this->request = $request;
+ $this->rule = $rule;
+ // 解析默认的URL规则
+ $dispatch = $this->parseUrl($dispatch);
+
+ parent::__construct($request, $rule, $dispatch, $this->param);
+ }
+
+ /**
+ * 解析URL地址
+ * @access protected
+ * @param string $url URL
+ * @return array
+ */
+ protected function parseUrl(string $url): array
+ {
+ $depr = $this->rule->config('pathinfo_depr');
+ $bind = $this->rule->getRouter()->getDomainBind();
+
+ if ($bind && preg_match('/^[a-z]/is', $bind)) {
+ $bind = str_replace('/', $depr, $bind);
+ // 如果有域名绑定
+ $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr);
+ }
+
+ $path = $this->rule->parseUrlPath($url);
+ if (empty($path)) {
+ return [null, null];
+ }
+
+ // 解析控制器
+ $controller = !empty($path) ? array_shift($path) : null;
+
+ if ($controller && !preg_match('/^[A-Za-z0-9][\w|\.]*$/', $controller)) {
+ throw new HttpException(404, 'controller not exists:' . $controller);
+ }
+
+ // 解析操作
+ $action = !empty($path) ? array_shift($path) : null;
+ $var = [];
+
+ // 解析额外参数
+ if ($path) {
+ preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) {
+ $var[$match[1]] = strip_tags($match[2]);
+ }, implode('|', $path));
+ }
+
+ $panDomain = $this->request->panDomain();
+ if ($panDomain && $key = array_search('*', $var)) {
+ // 泛域名赋值
+ $var[$key] = $panDomain;
+ }
+
+ // 设置当前请求的参数
+ $this->param = $var;
+
+ // 封装路由
+ $route = [$controller, $action];
+
+ if ($this->hasDefinedRoute($route)) {
+ throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url));
+ }
+
+ return $route;
+ }
+
+ /**
+ * 检查URL是否已经定义过路由
+ * @access protected
+ * @param array $route 路由信息
+ * @return bool
+ */
+ protected function hasDefinedRoute(array $route): bool
+ {
+ [$controller, $action] = $route;
+
+ // 检查地址是否被定义过路由
+ $name = strtolower(Str::studly($controller) . '/' . $action);
+
+ $host = $this->request->host(true);
+ $method = $this->request->method();
+
+ if ($this->rule->getRouter()->getName($name, $host, $method)) {
+ return true;
+ }
+
+ return false;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/service/ModelService.php b/vendor/topthink/framework/src/think/service/ModelService.php
new file mode 100644
index 000000000..87cfaf980
--- /dev/null
+++ b/vendor/topthink/framework/src/think/service/ModelService.php
@@ -0,0 +1,47 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\service;
+
+use think\Model;
+use think\Service;
+
+/**
+ * 模型服务类
+ */
+class ModelService extends Service
+{
+ public function boot()
+ {
+ Model::setDb($this->app->db);
+ Model::setEvent($this->app->event);
+ Model::setInvoker([$this->app, 'invoke']);
+ Model::maker(function (Model $model) {
+ $config = $this->app->config;
+
+ $isAutoWriteTimestamp = $model->getAutoWriteTimestamp();
+
+ if (is_null($isAutoWriteTimestamp)) {
+ // 自动写入时间戳
+ $model->isAutoWriteTimestamp($config->get('database.auto_timestamp', 'timestamp'));
+ }
+
+ $dateFormat = $model->getDateFormat();
+
+ if (is_null($dateFormat)) {
+ // 设置时间戳格式
+ $model->setDateFormat($config->get('database.datetime_format', 'Y-m-d H:i:s'));
+ }
+
+ });
+ }
+}
diff --git a/vendor/topthink/framework/src/think/service/PaginatorService.php b/vendor/topthink/framework/src/think/service/PaginatorService.php
new file mode 100644
index 000000000..a01977d01
--- /dev/null
+++ b/vendor/topthink/framework/src/think/service/PaginatorService.php
@@ -0,0 +1,52 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\service;
+
+use think\Paginator;
+use think\paginator\driver\Bootstrap;
+use think\Service;
+
+/**
+ * 分页服务类
+ */
+class PaginatorService extends Service
+{
+ public function register()
+ {
+ if (!$this->app->bound(Paginator::class)) {
+ $this->app->bind(Paginator::class, Bootstrap::class);
+ }
+ }
+
+ public function boot()
+ {
+ Paginator::maker(function (...$args) {
+ return $this->app->make(Paginator::class, $args, true);
+ });
+
+ Paginator::currentPathResolver(function () {
+ return $this->app->request->baseUrl();
+ });
+
+ Paginator::currentPageResolver(function ($varPage = 'page') {
+
+ $page = $this->app->request->param($varPage);
+
+ if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) {
+ return (int) $page;
+ }
+
+ return 1;
+ });
+ }
+}
diff --git a/vendor/topthink/framework/src/think/service/ValidateService.php b/vendor/topthink/framework/src/think/service/ValidateService.php
new file mode 100644
index 000000000..94d7638a6
--- /dev/null
+++ b/vendor/topthink/framework/src/think/service/ValidateService.php
@@ -0,0 +1,31 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\service;
+
+use think\Service;
+use think\Validate;
+
+/**
+ * 验证服务类
+ */
+class ValidateService extends Service
+{
+ public function boot()
+ {
+ Validate::maker(function (Validate $validate) {
+ $validate->setLang($this->app->lang);
+ $validate->setDb($this->app->db);
+ $validate->setRequest($this->app->request);
+ });
+ }
+}
diff --git a/vendor/topthink/framework/src/think/session/Store.php b/vendor/topthink/framework/src/think/session/Store.php
new file mode 100644
index 000000000..49e1ba909
--- /dev/null
+++ b/vendor/topthink/framework/src/think/session/Store.php
@@ -0,0 +1,340 @@
+
+// +----------------------------------------------------------------------
+
+namespace think\session;
+
+use think\contract\SessionHandlerInterface;
+use think\helper\Arr;
+
+class Store
+{
+
+ /**
+ * Session数据
+ * @var array
+ */
+ protected $data = [];
+
+ /**
+ * 是否初始化
+ * @var bool
+ */
+ protected $init = null;
+
+ /**
+ * 记录Session name
+ * @var string
+ */
+ protected $name = 'PHPSESSID';
+
+ /**
+ * 记录Session Id
+ * @var string
+ */
+ protected $id;
+
+ /**
+ * @var SessionHandlerInterface
+ */
+ protected $handler;
+
+ /** @var array */
+ protected $serialize = [];
+
+ public function __construct($name, SessionHandlerInterface $handler, array $serialize = null)
+ {
+ $this->name = $name;
+ $this->handler = $handler;
+
+ if (!empty($serialize)) {
+ $this->serialize = $serialize;
+ }
+
+ $this->setId();
+ }
+
+ /**
+ * 设置数据
+ * @access public
+ * @param array $data
+ * @return void
+ */
+ public function setData(array $data): void
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * session初始化
+ * @access public
+ * @return void
+ */
+ public function init(): void
+ {
+ // 读取缓存数据
+ $data = $this->handler->read($this->getId());
+
+ if (!empty($data)) {
+ $this->data = array_merge($this->data, $this->unserialize($data));
+ }
+
+ $this->init = true;
+ }
+
+ /**
+ * 设置SessionName
+ * @access public
+ * @param string $name session_name
+ * @return void
+ */
+ public function setName(string $name): void
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * 获取sessionName
+ * @access public
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * session_id设置
+ * @access public
+ * @param string $id session_id
+ * @return void
+ */
+ public function setId($id = null): void
+ {
+ $this->id = is_string($id) && strlen($id) === 32 && ctype_alnum($id) ? $id : md5(microtime(true) . session_create_id());
+ }
+
+ /**
+ * 获取session_id
+ * @access public
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * 获取所有数据
+ * @return array
+ */
+ public function all(): array
+ {
+ return $this->data;
+ }
+
+ /**
+ * session设置
+ * @access public
+ * @param string $name session名称
+ * @param mixed $value session值
+ * @return void
+ */
+ public function set(string $name, $value): void
+ {
+ Arr::set($this->data, $name, $value);
+ }
+
+ /**
+ * session获取
+ * @access public
+ * @param string $name session名称
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get(string $name, $default = null)
+ {
+ return Arr::get($this->data, $name, $default);
+ }
+
+ /**
+ * session获取并删除
+ * @access public
+ * @param string $name session名称
+ * @return mixed
+ */
+ public function pull(string $name)
+ {
+ return Arr::pull($this->data, $name);
+ }
+
+ /**
+ * 添加数据到一个session数组
+ * @access public
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ */
+ public function push(string $key, $value): void
+ {
+ $array = $this->get($key, []);
+
+ $array[] = $value;
+
+ $this->set($key, $array);
+ }
+
+ /**
+ * 判断session数据
+ * @access public
+ * @param string $name session名称
+ * @return bool
+ */
+ public function has(string $name): bool
+ {
+ return Arr::has($this->data, $name);
+ }
+
+ /**
+ * 删除session数据
+ * @access public
+ * @param string $name session名称
+ * @return void
+ */
+ public function delete(string $name): void
+ {
+ Arr::forget($this->data, $name);
+ }
+
+ /**
+ * 清空session数据
+ * @access public
+ * @return void
+ */
+ public function clear(): void
+ {
+ $this->data = [];
+ }
+
+ /**
+ * 销毁session
+ */
+ public function destroy(): void
+ {
+ $this->clear();
+
+ $this->regenerate(true);
+ }
+
+ /**
+ * 重新生成session id
+ * @param bool $destroy
+ */
+ public function regenerate(bool $destroy = false): void
+ {
+ if ($destroy) {
+ $this->handler->delete($this->getId());
+ }
+
+ $this->setId();
+ }
+
+ /**
+ * 保存session数据
+ * @access public
+ * @return void
+ */
+ public function save(): void
+ {
+ $this->clearFlashData();
+
+ $sessionId = $this->getId();
+
+ if (!empty($this->data)) {
+ $data = $this->serialize($this->data);
+
+ $this->handler->write($sessionId, $data);
+ } else {
+ $this->handler->delete($sessionId);
+ }
+
+ $this->init = false;
+ }
+
+ /**
+ * session设置 下一次请求有效
+ * @access public
+ * @param string $name session名称
+ * @param mixed $value session值
+ * @return void
+ */
+ public function flash(string $name, $value): void
+ {
+ $this->set($name, $value);
+ $this->push('__flash__.__next__', $name);
+ $this->set('__flash__.__current__', Arr::except($this->get('__flash__.__current__', []), $name));
+ }
+
+ /**
+ * 将本次闪存数据推迟到下次请求
+ *
+ * @return void
+ */
+ public function reflash(): void
+ {
+ $keys = $this->get('__flash__.__current__', []);
+ $values = array_unique(array_merge($this->get('__flash__.__next__', []), $keys));
+ $this->set('__flash__.__next__', $values);
+ $this->set('__flash__.__current__', []);
+ }
+
+ /**
+ * 清空当前请求的session数据
+ * @access public
+ * @return void
+ */
+ public function clearFlashData(): void
+ {
+ Arr::forget($this->data, $this->get('__flash__.__current__', []));
+ if (!empty($next = $this->get('__flash__.__next__', []))) {
+ $this->set('__flash__.__current__', $next);
+ } else {
+ $this->delete('__flash__.__current__');
+ }
+ $this->delete('__flash__.__next__');
+ }
+
+ /**
+ * 序列化数据
+ * @access protected
+ * @param mixed $data
+ * @return string
+ */
+ protected function serialize($data): string
+ {
+ $serialize = $this->serialize[0] ?? 'serialize';
+
+ return $serialize($data);
+ }
+
+ /**
+ * 反序列化数据
+ * @access protected
+ * @param string $data
+ * @return array
+ */
+ protected function unserialize(string $data): array
+ {
+ $unserialize = $this->serialize[1] ?? 'unserialize';
+
+ return (array) $unserialize($data);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/session/driver/Cache.php b/vendor/topthink/framework/src/think/session/driver/Cache.php
new file mode 100644
index 000000000..4fabc799a
--- /dev/null
+++ b/vendor/topthink/framework/src/think/session/driver/Cache.php
@@ -0,0 +1,50 @@
+
+// +----------------------------------------------------------------------
+namespace think\session\driver;
+
+use Psr\SimpleCache\CacheInterface;
+use think\contract\SessionHandlerInterface;
+use think\helper\Arr;
+
+class Cache implements SessionHandlerInterface
+{
+
+ /** @var CacheInterface */
+ protected $handler;
+
+ /** @var integer */
+ protected $expire;
+
+ /** @var string */
+ protected $prefix;
+
+ public function __construct(\think\Cache $cache, array $config = [])
+ {
+ $this->handler = $cache->store(Arr::get($config, 'store'));
+ $this->expire = Arr::get($config, 'expire', 1440);
+ $this->prefix = Arr::get($config, 'prefix', '');
+ }
+
+ public function read(string $sessionId): string
+ {
+ return (string) $this->handler->get($this->prefix . $sessionId);
+ }
+
+ public function delete(string $sessionId): bool
+ {
+ return $this->handler->delete($this->prefix . $sessionId);
+ }
+
+ public function write(string $sessionId, string $data): bool
+ {
+ return $this->handler->set($this->prefix . $sessionId, $data, $this->expire);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/session/driver/File.php b/vendor/topthink/framework/src/think/session/driver/File.php
new file mode 100644
index 000000000..788f3230e
--- /dev/null
+++ b/vendor/topthink/framework/src/think/session/driver/File.php
@@ -0,0 +1,249 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\session\driver;
+
+use Closure;
+use Exception;
+use FilesystemIterator;
+use Generator;
+use SplFileInfo;
+use think\App;
+use think\contract\SessionHandlerInterface;
+
+/**
+ * Session 文件驱动
+ */
+class File implements SessionHandlerInterface
+{
+ protected $config = [
+ 'path' => '',
+ 'expire' => 1440,
+ 'prefix' => '',
+ 'data_compress' => false,
+ 'gc_probability' => 1,
+ 'gc_divisor' => 100,
+ ];
+
+ public function __construct(App $app, array $config = [])
+ {
+ $this->config = array_merge($this->config, $config);
+
+ if (empty($this->config['path'])) {
+ $this->config['path'] = $app->getRuntimePath() . 'session' . DIRECTORY_SEPARATOR;
+ } elseif (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) {
+ $this->config['path'] .= DIRECTORY_SEPARATOR;
+ }
+
+ $this->init();
+ }
+
+ /**
+ * 打开Session
+ * @access protected
+ * @throws Exception
+ */
+ protected function init(): void
+ {
+ try {
+ !is_dir($this->config['path']) && mkdir($this->config['path'], 0755, true);
+ } catch (\Exception $e) {
+ // 写入失败
+ }
+
+ // 垃圾回收
+ if (random_int(1, $this->config['gc_divisor']) <= $this->config['gc_probability']) {
+ $this->gc();
+ }
+ }
+
+ /**
+ * Session 垃圾回收
+ * @access public
+ * @return void
+ */
+ public function gc(): void
+ {
+ $lifetime = $this->config['expire'];
+ $now = time();
+
+ $files = $this->findFiles($this->config['path'], function (SplFileInfo $item) use ($lifetime, $now) {
+ return $now - $lifetime > $item->getMTime();
+ });
+
+ foreach ($files as $file) {
+ $this->unlink($file->getPathname());
+ }
+ }
+
+ /**
+ * 查找文件
+ * @param string $root
+ * @param Closure $filter
+ * @return Generator
+ */
+ protected function findFiles(string $root, Closure $filter)
+ {
+ $items = new FilesystemIterator($root);
+
+ /** @var SplFileInfo $item */
+ foreach ($items as $item) {
+ if ($item->isDir() && !$item->isLink()) {
+ yield from $this->findFiles($item->getPathname(), $filter);
+ } else {
+ if ($filter($item)) {
+ yield $item;
+ }
+ }
+ }
+ }
+
+ /**
+ * 取得变量的存储文件名
+ * @access protected
+ * @param string $name 缓存变量名
+ * @param bool $auto 是否自动创建目录
+ * @return string
+ */
+ protected function getFileName(string $name, bool $auto = false): string
+ {
+ if ($this->config['prefix']) {
+ // 使用子目录
+ $name = $this->config['prefix'] . DIRECTORY_SEPARATOR . 'sess_' . $name;
+ } else {
+ $name = 'sess_' . $name;
+ }
+
+ $filename = $this->config['path'] . $name;
+ $dir = dirname($filename);
+
+ if ($auto && !is_dir($dir)) {
+ try {
+ mkdir($dir, 0755, true);
+ } catch (\Exception $e) {
+ // 创建失败
+ }
+ }
+
+ return $filename;
+ }
+
+ /**
+ * 读取Session
+ * @access public
+ * @param string $sessID
+ * @return string
+ */
+ public function read(string $sessID): string
+ {
+ $filename = $this->getFileName($sessID);
+
+ if (is_file($filename) && filemtime($filename) >= time() - $this->config['expire']) {
+ $content = $this->readFile($filename);
+
+ if ($this->config['data_compress'] && function_exists('gzcompress')) {
+ //启用数据压缩
+ $content = (string) gzuncompress($content);
+ }
+
+ return $content;
+ }
+
+ return '';
+ }
+
+ /**
+ * 写文件(加锁)
+ * @param $path
+ * @param $content
+ * @return bool
+ */
+ protected function writeFile($path, $content): bool
+ {
+ return (bool) file_put_contents($path, $content, LOCK_EX);
+ }
+
+ /**
+ * 读取文件内容(加锁)
+ * @param $path
+ * @return string
+ */
+ protected function readFile($path): string
+ {
+ $contents = '';
+
+ $handle = fopen($path, 'rb');
+
+ if ($handle) {
+ try {
+ if (flock($handle, LOCK_SH)) {
+ clearstatcache(true, $path);
+
+ $contents = fread($handle, filesize($path) ?: 1);
+
+ flock($handle, LOCK_UN);
+ }
+ } finally {
+ fclose($handle);
+ }
+ }
+
+ return $contents;
+ }
+
+ /**
+ * 写入Session
+ * @access public
+ * @param string $sessID
+ * @param string $sessData
+ * @return bool
+ */
+ public function write(string $sessID, string $sessData): bool
+ {
+ $filename = $this->getFileName($sessID, true);
+ $data = $sessData;
+
+ if ($this->config['data_compress'] && function_exists('gzcompress')) {
+ //数据压缩
+ $data = gzcompress($data, 3);
+ }
+
+ return $this->writeFile($filename, $data);
+ }
+
+ /**
+ * 删除Session
+ * @access public
+ * @param string $sessID
+ * @return bool
+ */
+ public function delete(string $sessID): bool
+ {
+ try {
+ return $this->unlink($this->getFileName($sessID));
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * 判断文件是否存在后,删除
+ * @access private
+ * @param string $file
+ * @return bool
+ */
+ private function unlink(string $file): bool
+ {
+ return is_file($file) && unlink($file);
+ }
+
+}
diff --git a/thinkphp/library/think/validate/ValidateRule.php b/vendor/topthink/framework/src/think/validate/ValidateRule.php
old mode 100755
new mode 100644
similarity index 95%
rename from thinkphp/library/think/validate/ValidateRule.php
rename to vendor/topthink/framework/src/think/validate/ValidateRule.php
index 7cd701747..b741f5301
--- a/thinkphp/library/think/validate/ValidateRule.php
+++ b/vendor/topthink/framework/src/think/validate/ValidateRule.php
@@ -2,12 +2,13 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\validate;
@@ -90,7 +91,7 @@ class ValidateRule
* @param string $msg 提示信息
* @return $this
*/
- protected function addItem($name, $rule = null, $msg = '')
+ protected function addItem(string $name, $rule = null, string $msg = '')
{
if ($rule || 0 === $rule) {
$this->rule[$name] = $rule;
@@ -108,7 +109,7 @@ class ValidateRule
* @access public
* @return array
*/
- public function getRule()
+ public function getRule(): array
{
return $this->rule;
}
@@ -118,9 +119,9 @@ class ValidateRule
* @access public
* @return string
*/
- public function getTitle()
+ public function getTitle(): string
{
- return $this->title;
+ return $this->title ?: '';
}
/**
@@ -128,7 +129,7 @@ class ValidateRule
* @access public
* @return array
*/
- public function getMsg()
+ public function getMsg(): array
{
return $this->message;
}
@@ -138,7 +139,7 @@ class ValidateRule
* @access public
* @return $this
*/
- public function title($title)
+ public function title(string $title)
{
$this->title = $title;
diff --git a/vendor/topthink/framework/src/think/view/driver/Php.php b/vendor/topthink/framework/src/think/view/driver/Php.php
new file mode 100644
index 000000000..9e6e54aa3
--- /dev/null
+++ b/vendor/topthink/framework/src/think/view/driver/Php.php
@@ -0,0 +1,191 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\view\driver;
+
+use RuntimeException;
+use think\App;
+use think\contract\TemplateHandlerInterface;
+use think\helper\Str;
+
+/**
+ * PHP原生模板驱动
+ */
+class Php implements TemplateHandlerInterface
+{
+ protected $template;
+ protected $content;
+ protected $app;
+
+ // 模板引擎参数
+ protected $config = [
+ // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法
+ 'auto_rule' => 1,
+ // 视图目录名
+ 'view_dir_name' => 'view',
+ // 应用模板路径
+ 'view_path' => '',
+ // 模板文件后缀
+ 'view_suffix' => 'php',
+ // 模板文件名分隔符
+ 'view_depr' => DIRECTORY_SEPARATOR,
+ ];
+
+ public function __construct(App $app, array $config = [])
+ {
+ $this->app = $app;
+ $this->config = array_merge($this->config, (array) $config);
+ }
+
+ /**
+ * 检测是否存在模板文件
+ * @access public
+ * @param string $template 模板文件或者模板规则
+ * @return bool
+ */
+ public function exists(string $template): bool
+ {
+ if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
+ // 获取模板文件名
+ $template = $this->parseTemplate($template);
+ }
+
+ return is_file($template);
+ }
+
+ /**
+ * 渲染模板文件
+ * @access public
+ * @param string $template 模板文件
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function fetch(string $template, array $data = []): void
+ {
+ if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
+ // 获取模板文件名
+ $template = $this->parseTemplate($template);
+ }
+
+ // 模板不存在 抛出异常
+ if (!is_file($template)) {
+ throw new RuntimeException('template not exists:' . $template);
+ }
+
+ $this->template = $template;
+
+ extract($data, EXTR_OVERWRITE);
+
+ include $this->template;
+ }
+
+ /**
+ * 渲染模板内容
+ * @access public
+ * @param string $content 模板内容
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function display(string $content, array $data = []): void
+ {
+ $this->content = $content;
+
+ extract($data, EXTR_OVERWRITE);
+ eval('?>' . $this->content);
+ }
+
+ /**
+ * 自动定位模板文件
+ * @access private
+ * @param string $template 模板文件规则
+ * @return string
+ */
+ private function parseTemplate(string $template): string
+ {
+ $request = $this->app->request;
+
+ // 获取视图根目录
+ if (strpos($template, '@')) {
+ // 跨应用调用
+ [$app, $template] = explode('@', $template);
+ }
+
+ if ($this->config['view_path'] && !isset($app)) {
+ $path = $this->config['view_path'];
+ } else {
+ $appName = isset($app) ? $app : $this->app->http->getName();
+ $view = $this->config['view_dir_name'];
+
+ if (is_dir($this->app->getAppPath() . $view)) {
+ $path = isset($app) ? $this->app->getBasePath() . ($appName ? $appName . DIRECTORY_SEPARATOR : '') . $view . DIRECTORY_SEPARATOR : $this->app->getAppPath() . $view . DIRECTORY_SEPARATOR;
+ } else {
+ $path = $this->app->getRootPath() . $view . DIRECTORY_SEPARATOR . ($appName ? $appName . DIRECTORY_SEPARATOR : '');
+ }
+ }
+
+ $depr = $this->config['view_depr'];
+
+ if (0 !== strpos($template, '/')) {
+ $template = str_replace(['/', ':'], $depr, $template);
+ $controller = $request->controller();
+ if (strpos($controller, '.')) {
+ $pos = strrpos($controller, '.');
+ $controller = substr($controller, 0, $pos) . '.' . Str::snake(substr($controller, $pos + 1));
+ } else {
+ $controller = Str::snake($controller);
+ }
+
+ if ($controller) {
+ if ('' == $template) {
+ // 如果模板文件名为空 按照默认规则定位
+ if (2 == $this->config['auto_rule']) {
+ $template = $request->action(true);
+ } elseif (3 == $this->config['auto_rule']) {
+ $template = $request->action();
+ } else {
+ $template = Str::snake($request->action());
+ }
+
+ $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template;
+ } elseif (false === strpos($template, $depr)) {
+ $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template;
+ }
+ }
+ } else {
+ $template = str_replace(['/', ':'], $depr, substr($template, 1));
+ }
+
+ return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.');
+ }
+
+ /**
+ * 配置模板引擎
+ * @access private
+ * @param array $config 参数
+ * @return void
+ */
+ public function config(array $config): void
+ {
+ $this->config = array_merge($this->config, $config);
+ }
+
+ /**
+ * 获取模板引擎配置
+ * @access public
+ * @param string $name 参数名
+ * @return mixed
+ */
+ public function getConfig(string $name)
+ {
+ return $this->config[$name] ?? null;
+ }
+}
diff --git a/vendor/topthink/framework/src/tpl/think_exception.tpl b/vendor/topthink/framework/src/tpl/think_exception.tpl
new file mode 100644
index 000000000..7766caf57
--- /dev/null
+++ b/vendor/topthink/framework/src/tpl/think_exception.tpl
@@ -0,0 +1,502 @@
+'.end($names).'';
+ }
+}
+
+if (!function_exists('parse_file')) {
+ function parse_file($file, $line)
+ {
+ return ''.basename($file)." line {$line}".' ';
+ }
+}
+
+if (!function_exists('parse_args')) {
+ function parse_args($args)
+ {
+ $result = [];
+ foreach ($args as $key => $item) {
+ switch (true) {
+ case is_object($item):
+ $value = sprintf('object (%s)', parse_class(get_class($item)));
+ break;
+ case is_array($item):
+ if (count($item) > 3) {
+ $value = sprintf('[%s, ...]', parse_args(array_slice($item, 0, 3)));
+ } else {
+ $value = sprintf('[%s]', parse_args($item));
+ }
+ break;
+ case is_string($item):
+ if (strlen($item) > 20) {
+ $value = sprintf(
+ '\'%s... \'',
+ htmlentities($item),
+ htmlentities(substr($item, 0, 20))
+ );
+ } else {
+ $value = sprintf("'%s'", htmlentities($item));
+ }
+ break;
+ case is_int($item):
+ case is_float($item):
+ $value = $item;
+ break;
+ case is_null($item):
+ $value = 'null ';
+ break;
+ case is_bool($item):
+ $value = '' . ($item ? 'true' : 'false') . ' ';
+ break;
+ case is_resource($item):
+ $value = 'resource ';
+ break;
+ default:
+ $value = htmlentities(str_replace("\n", '', var_export(strval($item), true)));
+ break;
+ }
+
+ $result[] = is_int($key) ? $value : "'{$key}' => {$value}";
+ }
+
+ return implode(', ', $result);
+ }
+}
+if (!function_exists('echo_value')) {
+ function echo_value($val)
+ {
+ if (is_array($val) || is_object($val)) {
+ echo htmlentities(json_encode($val, JSON_PRETTY_PRINT));
+ } elseif (is_bool($val)) {
+ echo $val ? 'true' : 'false';
+ } elseif (is_scalar($val)) {
+ echo htmlentities($val);
+ } else {
+ echo 'Resource';
+ }
+ }
+}
+?>
+
+
+
+
+ 系统发生错误
+
+
+
+
+
+ $trace) { ?>
+
+
+
+
+
+
+
Call Stack
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Exception Datas
+ $value) { ?>
+
+
+ empty
+
+
+
+ $val) { ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Environment Variables
+ $value) { ?>
+
+
+ empty
+
+
+
+ $val) { ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vendor/topthink/framework/tests/AppTest.php b/vendor/topthink/framework/tests/AppTest.php
new file mode 100644
index 000000000..6b8601521
--- /dev/null
+++ b/vendor/topthink/framework/tests/AppTest.php
@@ -0,0 +1,215 @@
+ 'class',
+ ];
+
+ public function register()
+ {
+
+ }
+
+ public function boot()
+ {
+
+ }
+}
+
+/**
+ * @property array initializers
+ */
+class AppTest extends TestCase
+{
+ /** @var App */
+ protected $app;
+
+ protected function setUp()
+ {
+ $this->app = new App();
+ }
+
+ protected function tearDown(): void
+ {
+ m::close();
+ }
+
+ public function testService()
+ {
+ $this->app->register(stdClass::class);
+
+ $this->assertInstanceOf(stdClass::class, $this->app->getService(stdClass::class));
+
+ $service = m::mock(SomeService::class);
+
+ $service->shouldReceive('register')->once();
+
+ $this->app->register($service);
+
+ $this->assertEquals($service, $this->app->getService(SomeService::class));
+
+ $service2 = m::mock(SomeService::class);
+
+ $service2->shouldReceive('register')->once();
+
+ $this->app->register($service2);
+
+ $this->assertEquals($service, $this->app->getService(SomeService::class));
+
+ $this->app->register($service2, true);
+
+ $this->assertEquals($service2, $this->app->getService(SomeService::class));
+
+ $service->shouldReceive('boot')->once();
+ $service2->shouldReceive('boot')->once();
+
+ $this->app->boot();
+ }
+
+ public function testDebug()
+ {
+ $this->app->debug(false);
+
+ $this->assertFalse($this->app->isDebug());
+
+ $this->app->debug(true);
+
+ $this->assertTrue($this->app->isDebug());
+ }
+
+ public function testNamespace()
+ {
+ $namespace = 'test';
+
+ $this->app->setNamespace($namespace);
+
+ $this->assertEquals($namespace, $this->app->getNamespace());
+ }
+
+ public function testVersion()
+ {
+ $this->assertEquals(App::VERSION, $this->app->version());
+ }
+
+ public function testPath()
+ {
+ $rootPath = __DIR__ . DIRECTORY_SEPARATOR;
+
+ $app = new App($rootPath);
+
+ $this->assertEquals($rootPath, $app->getRootPath());
+
+ $this->assertEquals(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR, $app->getThinkPath());
+
+ $this->assertEquals($rootPath . 'app' . DIRECTORY_SEPARATOR, $app->getAppPath());
+
+ $appPath = $rootPath . 'app' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR;
+ $app->setAppPath($appPath);
+ $this->assertEquals($appPath, $app->getAppPath());
+
+ $this->assertEquals($rootPath . 'app' . DIRECTORY_SEPARATOR, $app->getBasePath());
+
+ $this->assertEquals($rootPath . 'config' . DIRECTORY_SEPARATOR, $app->getConfigPath());
+
+ $this->assertEquals($rootPath . 'runtime' . DIRECTORY_SEPARATOR, $app->getRuntimePath());
+
+ $runtimePath = $rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR;
+ $app->setRuntimePath($runtimePath);
+ $this->assertEquals($runtimePath, $app->getRuntimePath());
+ }
+
+ /**
+ * @param vfsStreamDirectory $root
+ * @param bool $debug
+ * @return App
+ */
+ protected function prepareAppForInitialize(vfsStreamDirectory $root, $debug = true)
+ {
+ $rootPath = $root->url() . DIRECTORY_SEPARATOR;
+
+ $app = new App($rootPath);
+
+ $initializer = m::mock();
+ $initializer->shouldReceive('init')->once()->with($app);
+
+ $app->instance($initializer->mockery_getName(), $initializer);
+
+ (function () use ($initializer) {
+ $this->initializers = [$initializer->mockery_getName()];
+ })->call($app);
+
+ $env = m::mock(Env::class);
+ $env->shouldReceive('load')->once()->with($rootPath . '.env');
+ $env->shouldReceive('get')->once()->with('config_ext', '.php')->andReturn('.php');
+ $env->shouldReceive('get')->once()->with('app_debug')->andReturn($debug);
+
+ $event = m::mock(Event::class);
+ $event->shouldReceive('trigger')->once()->with(AppInit::class);
+ $event->shouldReceive('bind')->once()->with([]);
+ $event->shouldReceive('listenEvents')->once()->with([]);
+ $event->shouldReceive('subscribe')->once()->with([]);
+
+ $app->instance('env', $env);
+ $app->instance('event', $event);
+
+ return $app;
+ }
+
+ public function testInitialize()
+ {
+ $root = vfsStream::setup('rootDir', null, [
+ '.env' => '',
+ 'app' => [
+ 'common.php' => '',
+ 'event.php' => '[],"listen"=>[],"subscribe"=>[]];',
+ 'provider.php' => ' [
+ 'app.php' => 'prepareAppForInitialize($root, true);
+
+ $app->debug(false);
+
+ $app->initialize();
+
+ $this->assertIsInt($app->getBeginMem());
+ $this->assertIsFloat($app->getBeginTime());
+
+ $this->assertTrue($app->initialized());
+ }
+
+ public function testFactory()
+ {
+ $this->assertInstanceOf(stdClass::class, App::factory(stdClass::class));
+
+ $this->expectException(ClassNotFoundException::class);
+
+ App::factory('SomeClass');
+ }
+
+ public function testParseClass()
+ {
+ $this->assertEquals('app\\controller\\SomeClass', $this->app->parseClass('controller', 'some_class'));
+ $this->app->setNamespace('app2');
+ $this->assertEquals('app2\\controller\\SomeClass', $this->app->parseClass('controller', 'some_class'));
+ }
+
+}
diff --git a/vendor/topthink/framework/tests/CacheTest.php b/vendor/topthink/framework/tests/CacheTest.php
new file mode 100644
index 000000000..5b5a13cbc
--- /dev/null
+++ b/vendor/topthink/framework/tests/CacheTest.php
@@ -0,0 +1,149 @@
+app = m::mock(App::class)->makePartial();
+
+ Container::setInstance($this->app);
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->config = m::mock(Config::class)->makePartial();
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+
+ $this->cache = new Cache($this->app);
+ }
+
+ public function testGetConfig()
+ {
+ $config = [
+ 'default' => 'file',
+ ];
+
+ $this->config->shouldReceive('get')->with('cache')->andReturn($config);
+
+ $this->assertEquals($config, $this->cache->getConfig());
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->cache->getStoreConfig('foo');
+ }
+
+ public function testCacheManagerInstances()
+ {
+ $this->config->shouldReceive('get')->with("cache.stores.single", null)->andReturn(['type' => 'file']);
+
+ $channel1 = $this->cache->store('single');
+ $channel2 = $this->cache->store('single');
+
+ $this->assertSame($channel1, $channel2);
+ }
+
+ public function testFileCache()
+ {
+ $root = vfsStream::setup();
+
+ $this->config->shouldReceive('get')->with("cache.default", null)->andReturn('file');
+
+ $this->config->shouldReceive('get')->with("cache.stores.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]);
+
+ $this->cache->set('foo', 5);
+ $this->cache->inc('foo');
+ $this->assertEquals(6, $this->cache->get('foo'));
+ $this->cache->dec('foo', 2);
+ $this->assertEquals(4, $this->cache->get('foo'));
+
+ $this->cache->set('bar', true);
+ $this->assertTrue($this->cache->get('bar'));
+
+ $this->cache->set('baz', null);
+ $this->assertNull($this->cache->get('baz'));
+
+ $this->assertTrue($this->cache->has('baz'));
+ $this->cache->delete('baz');
+ $this->assertFalse($this->cache->has('baz'));
+ $this->assertNull($this->cache->get('baz'));
+ $this->assertFalse($this->cache->get('baz', false));
+
+ $this->assertTrue($root->hasChildren());
+ $this->cache->clear();
+ $this->assertFalse($root->hasChildren());
+
+ //tags
+ $this->cache->tag('foo')->set('bar', 'foobar');
+ $this->assertEquals('foobar', $this->cache->get('bar'));
+ $this->cache->tag('foo')->clear();
+ $this->assertFalse($this->cache->has('bar'));
+
+ //multiple
+ $this->cache->setMultiple(['foo' => ['foobar', 'bar'], 'foobar' => ['foo', 'bar']]);
+ $this->assertEquals(['foo' => ['foobar', 'bar'], 'foobar' => ['foo', 'bar']], $this->cache->getMultiple(['foo', 'foobar']));
+ $this->assertTrue($this->cache->deleteMultiple(['foo', 'foobar']));
+ }
+
+ public function testRedisCache()
+ {
+ if (extension_loaded('redis')) {
+ return;
+ }
+ $this->config->shouldReceive('get')->with("cache.default", null)->andReturn('redis');
+ $this->config->shouldReceive('get')->with("cache.stores.redis", null)->andReturn(['type' => 'redis']);
+
+ $redis = m::mock('overload:\Predis\Client');
+
+ $redis->shouldReceive("set")->once()->with('foo', 5)->andReturnTrue();
+ $redis->shouldReceive("incrby")->once()->with('foo', 1)->andReturnTrue();
+ $redis->shouldReceive("decrby")->once()->with('foo', 2)->andReturnTrue();
+ $redis->shouldReceive("get")->once()->with('foo')->andReturn('6');
+ $redis->shouldReceive("get")->once()->with('foo')->andReturn('4');
+ $redis->shouldReceive("set")->once()->with('bar', serialize(true))->andReturnTrue();
+ $redis->shouldReceive("set")->once()->with('baz', serialize(null))->andReturnTrue();
+ $redis->shouldReceive("del")->once()->with('baz')->andReturnTrue();
+ $redis->shouldReceive("flushDB")->once()->andReturnTrue();
+ $redis->shouldReceive("set")->once()->with('bar', serialize('foobar'))->andReturnTrue();
+ $redis->shouldReceive("sAdd")->once()->with('tag:' . md5('foo'), 'bar')->andReturnTrue();
+ $redis->shouldReceive("sMembers")->once()->with('tag:' . md5('foo'))->andReturn(['bar']);
+ $redis->shouldReceive("del")->once()->with(['bar'])->andReturnTrue();
+ $redis->shouldReceive("del")->once()->with('tag:' . md5('foo'))->andReturnTrue();
+
+ $this->cache->set('foo', 5);
+ $this->cache->inc('foo');
+ $this->assertEquals(6, $this->cache->get('foo'));
+ $this->cache->dec('foo', 2);
+ $this->assertEquals(4, $this->cache->get('foo'));
+
+ $this->cache->set('bar', true);
+ $this->cache->set('baz', null);
+ $this->cache->delete('baz');
+ $this->cache->clear();
+
+ //tags
+ $this->cache->tag('foo')->set('bar', 'foobar');
+ $this->cache->tag('foo')->clear();
+ }
+}
diff --git a/vendor/topthink/framework/tests/ConfigTest.php b/vendor/topthink/framework/tests/ConfigTest.php
new file mode 100644
index 000000000..271a34fc3
--- /dev/null
+++ b/vendor/topthink/framework/tests/ConfigTest.php
@@ -0,0 +1,46 @@
+setContent(" 'value1','key2'=>'value2'];");
+ $root->addChild($file);
+
+ $config = new Config();
+
+ $config->load($file->url(), 'test');
+
+ $this->assertEquals('value1', $config->get('test.key1'));
+ $this->assertEquals('value2', $config->get('test.key2'));
+
+ $this->assertSame(['key1' => 'value1', 'key2' => 'value2'], $config->get('test'));
+ }
+
+ public function testSetAndGet()
+ {
+ $config = new Config();
+
+ $config->set([
+ 'key1' => 'value1',
+ 'key2' => [
+ 'key3' => 'value3',
+ ],
+ ], 'test');
+
+ $this->assertTrue($config->has('test.key1'));
+ $this->assertEquals('value1', $config->get('test.key1'));
+ $this->assertEquals('value3', $config->get('test.key2.key3'));
+
+ $this->assertEquals(['key3' => 'value3'], $config->get('test.key2'));
+ $this->assertFalse($config->has('test.key3'));
+ $this->assertEquals('none', $config->get('test.key3', 'none'));
+ }
+}
diff --git a/vendor/topthink/framework/tests/ContainerTest.php b/vendor/topthink/framework/tests/ContainerTest.php
new file mode 100644
index 000000000..e27deb088
--- /dev/null
+++ b/vendor/topthink/framework/tests/ContainerTest.php
@@ -0,0 +1,314 @@
+name = $name;
+ }
+
+ public function some(Container $container)
+ {
+ }
+
+ protected function protectionFun()
+ {
+ return true;
+ }
+
+ public static function test(Container $container)
+ {
+ return $container;
+ }
+
+ public static function __make()
+ {
+ return new self('Taylor');
+ }
+}
+
+class SomeClass
+{
+ public $container;
+
+ public $count = 0;
+
+ public function __construct(Container $container)
+ {
+ $this->container = $container;
+ }
+}
+
+class ContainerTest extends TestCase
+{
+ protected function tearDown(): void
+ {
+ Container::setInstance(null);
+ }
+
+ public function testClosureResolution()
+ {
+ $container = new Container;
+
+ Container::setInstance($container);
+
+ $container->bind('name', function () {
+ return 'Taylor';
+ });
+
+ $this->assertEquals('Taylor', $container->make('name'));
+
+ $this->assertEquals('Taylor', Container::pull('name'));
+ }
+
+ public function testGet()
+ {
+ $container = new Container;
+
+ $this->expectException(ClassNotFoundException::class);
+ $this->expectExceptionMessage('class not exists: name');
+ $container->get('name');
+
+ $container->bind('name', function () {
+ return 'Taylor';
+ });
+
+ $this->assertSame('Taylor', $container->get('name'));
+ }
+
+ public function testExist()
+ {
+ $container = new Container;
+
+ $container->bind('name', function () {
+ return 'Taylor';
+ });
+
+ $this->assertFalse($container->exists("name"));
+
+ $container->make('name');
+
+ $this->assertTrue($container->exists('name'));
+ }
+
+ public function testInstance()
+ {
+ $container = new Container;
+
+ $container->bind('name', function () {
+ return 'Taylor';
+ });
+
+ $this->assertEquals('Taylor', $container->get('name'));
+
+ $container->bind('name2', Taylor::class);
+
+ $object = new stdClass();
+
+ $this->assertFalse($container->exists('name2'));
+
+ $container->instance('name2', $object);
+
+ $this->assertTrue($container->exists('name2'));
+
+ $this->assertTrue($container->exists(Taylor::class));
+
+ $this->assertEquals($object, $container->make(Taylor::class));
+
+ unset($container->name1);
+
+ $this->assertFalse($container->exists('name1'));
+
+ $container->delete('name2');
+
+ $this->assertFalse($container->exists('name2'));
+
+ foreach ($container as $class => $instance) {
+
+ }
+ }
+
+ public function testBind()
+ {
+ $container = new Container;
+
+ $object = new stdClass();
+
+ $container->bind(['name' => Taylor::class]);
+
+ $container->bind('name2', $object);
+
+ $container->bind('name3', Taylor::class);
+ $container->bind('name3', Taylor::class);
+
+ $container->name4 = $object;
+
+ $container['name5'] = $object;
+
+ $this->assertTrue(isset($container->name4));
+
+ $this->assertTrue(isset($container['name5']));
+
+ $this->assertInstanceOf(Taylor::class, $container->get('name'));
+
+ $this->assertSame($object, $container->get('name2'));
+
+ $this->assertSame($object, $container->name4);
+
+ $this->assertSame($object, $container['name5']);
+
+ $this->assertInstanceOf(Taylor::class, $container->get('name3'));
+
+ unset($container['name']);
+
+ $this->assertFalse(isset($container['name']));
+
+ unset($container->name3);
+
+ $this->assertFalse(isset($container->name3));
+ }
+
+ public function testAutoConcreteResolution()
+ {
+ $container = new Container;
+
+ $taylor = $container->make(Taylor::class);
+
+ $this->assertInstanceOf(Taylor::class, $taylor);
+ $this->assertAttributeSame('Taylor', 'name', $taylor);
+ }
+
+ public function testGetAndSetInstance()
+ {
+ $this->assertInstanceOf(Container::class, Container::getInstance());
+
+ $object = new stdClass();
+
+ Container::setInstance($object);
+
+ $this->assertSame($object, Container::getInstance());
+
+ Container::setInstance(function () {
+ return $this;
+ });
+
+ $this->assertSame($this, Container::getInstance());
+ }
+
+ public function testResolving()
+ {
+ $container = new Container();
+ $container->bind(Container::class, $container);
+
+ $container->resolving(function (SomeClass $taylor, Container $container) {
+ $taylor->count++;
+ });
+ $container->resolving(SomeClass::class, function (SomeClass $taylor, Container $container) {
+ $taylor->count++;
+ });
+
+ /** @var SomeClass $someClass */
+ $someClass = $container->invokeClass(SomeClass::class);
+ $this->assertEquals(2, $someClass->count);
+ }
+
+ public function testInvokeFunctionWithoutMethodThrowsException()
+ {
+ $this->expectException(FuncNotFoundException::class);
+ $this->expectExceptionMessage('function not exists: ContainerTestCallStub()');
+ $container = new Container();
+ $container->invokeFunction('ContainerTestCallStub', []);
+ }
+
+ public function testInvokeProtectionMethod()
+ {
+ $container = new Container();
+ $this->assertTrue($container->invokeMethod([Taylor::class, 'protectionFun'], [], true));
+ }
+
+ public function testInvoke()
+ {
+ $container = new Container();
+
+ Container::setInstance($container);
+
+ $container->bind(Container::class, $container);
+
+ $stub = $this->createMock(Taylor::class);
+
+ $stub->expects($this->once())->method('some')->with($container)->will($this->returnSelf());
+
+ $container->invokeMethod([$stub, 'some']);
+
+ $this->assertEquals('48', $container->invoke('ord', ['0']));
+
+ $this->assertSame($container, $container->invoke(Taylor::class . '::test', []));
+
+ $this->assertSame($container, $container->invokeMethod(Taylor::class . '::test'));
+
+ $reflect = new ReflectionMethod($container, 'exists');
+
+ $this->assertTrue($container->invokeReflectMethod($container, $reflect, [Container::class]));
+
+ $this->assertSame($container, $container->invoke(function (Container $container) {
+ return $container;
+ }));
+
+ $this->assertSame($container, $container->invoke(Taylor::class . '::test'));
+
+ $object = $container->invokeClass(SomeClass::class);
+ $this->assertInstanceOf(SomeClass::class, $object);
+ $this->assertSame($container, $object->container);
+
+ $stdClass = new stdClass();
+
+ $container->invoke(function (Container $container, stdClass $stdObject, $key1, $lowKey, $key2 = 'default') use ($stdClass) {
+ $this->assertEquals('value1', $key1);
+ $this->assertEquals('default', $key2);
+ $this->assertEquals('value2', $lowKey);
+ $this->assertSame($stdClass, $stdObject);
+ return $container;
+ }, ['some' => $stdClass, 'key1' => 'value1', 'low_key' => 'value2']);
+ }
+
+ public function testInvokeMethodNotExists()
+ {
+ $container = $this->resolveContainer();
+ $this->expectException(FuncNotFoundException::class);
+
+ $container->invokeMethod([SomeClass::class, 'any']);
+ }
+
+ public function testInvokeClassNotExists()
+ {
+ $container = new Container();
+
+ Container::setInstance($container);
+
+ $container->bind(Container::class, $container);
+
+ $this->expectExceptionObject(new ClassNotFoundException('class not exists: SomeClass'));
+
+ $container->invokeClass('SomeClass');
+ }
+
+ protected function resolveContainer()
+ {
+ $container = new Container();
+
+ Container::setInstance($container);
+ return $container;
+ }
+
+}
diff --git a/vendor/topthink/framework/tests/DbTest.php b/vendor/topthink/framework/tests/DbTest.php
new file mode 100644
index 000000000..3bd0c1e9d
--- /dev/null
+++ b/vendor/topthink/framework/tests/DbTest.php
@@ -0,0 +1,49 @@
+shouldReceive('get')->with('database.cache_store', null)->andReturn(null);
+ $cache->shouldReceive('store')->with(null)->andReturn($store);
+
+ $db = Db::__make($event, $config, $log, $cache);
+
+ $config->shouldReceive('get')->with('database.foo', null)->andReturn('foo');
+ $this->assertEquals('foo', $db->getConfig('foo'));
+
+ $config->shouldReceive('get')->with('database', [])->andReturn([]);
+ $this->assertEquals([], $db->getConfig());
+
+ $callback = function () {
+ };
+ $event->shouldReceive('listen')->with('db.some', $callback);
+ $db->event('some', $callback);
+
+ $event->shouldReceive('trigger')->with('db.some', null, false);
+ $db->trigger('some');
+ }
+
+}
diff --git a/vendor/topthink/framework/tests/EnvTest.php b/vendor/topthink/framework/tests/EnvTest.php
new file mode 100644
index 000000000..cf2e65f85
--- /dev/null
+++ b/vendor/topthink/framework/tests/EnvTest.php
@@ -0,0 +1,82 @@
+setContent("key1=value1\nkey2=value2");
+ $root->addChild($envFile);
+
+ $env = new Env();
+
+ $env->load($envFile->url());
+
+ $this->assertEquals('value1', $env->get('key1'));
+ $this->assertEquals('value2', $env->get('key2'));
+
+ $this->assertSame(['KEY1' => 'value1', 'KEY2' => 'value2'], $env->get());
+ }
+
+ public function testServerEnv()
+ {
+ $env = new Env();
+
+ $this->assertEquals('value2', $env->get('key2', 'value2'));
+
+ putenv('PHP_KEY7=value7');
+ putenv('PHP_KEY8=false');
+ putenv('PHP_KEY9=true');
+
+ $this->assertEquals('value7', $env->get('key7'));
+ $this->assertFalse($env->get('KEY8'));
+ $this->assertTrue($env->get('key9'));
+ }
+
+ public function testSetEnv()
+ {
+ $env = new Env();
+
+ $env->set([
+ 'key1' => 'value1',
+ 'key2' => [
+ 'key1' => 'value1-2',
+ ],
+ ]);
+
+ $env->set('key3', 'value3');
+
+ $env->key4 = 'value4';
+
+ $env['key5'] = 'value5';
+
+ $this->assertEquals('value1', $env->get('key1'));
+ $this->assertEquals('value1-2', $env->get('key2.key1'));
+
+ $this->assertEquals('value3', $env->get('key3'));
+
+ $this->assertEquals('value4', $env->key4);
+
+ $this->assertEquals('value5', $env['key5']);
+
+ $this->expectException(Exception::class);
+
+ unset($env['key5']);
+ }
+
+ public function testHasEnv()
+ {
+ $env = new Env();
+ $env->set(['foo' => 'bar']);
+ $this->assertTrue($env->has('foo'));
+ $this->assertTrue(isset($env->foo));
+ $this->assertTrue($env->offsetExists('foo'));
+ }
+}
diff --git a/vendor/topthink/framework/tests/EventTest.php b/vendor/topthink/framework/tests/EventTest.php
new file mode 100644
index 000000000..ded5a36d5
--- /dev/null
+++ b/vendor/topthink/framework/tests/EventTest.php
@@ -0,0 +1,134 @@
+app = m::mock(App::class)->makePartial();
+
+ Container::setInstance($this->app);
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->config = m::mock(Config::class)->makePartial();
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+
+ $this->event = new Event($this->app);
+ }
+
+ public function testBasic()
+ {
+ $this->event->bind(['foo' => 'baz']);
+
+ $this->event->listen('foo', function ($bar) {
+ $this->assertEquals('bar', $bar);
+ });
+
+ $this->assertTrue($this->event->hasListener('foo'));
+
+ $this->event->trigger('baz', 'bar');
+
+ $this->event->remove('foo');
+
+ $this->assertFalse($this->event->hasListener('foo'));
+ }
+
+ public function testOnceEvent()
+ {
+ $this->event->listen('AppInit', function ($bar) {
+ $this->assertEquals('bar', $bar);
+ return 'foo';
+ });
+
+ $this->assertEquals('foo', $this->event->trigger('AppInit', 'bar', true));
+ $this->assertEquals(['foo'], $this->event->trigger('AppInit', 'bar'));
+ }
+
+ public function testClassListener()
+ {
+ $listener = m::mock("overload:SomeListener", TestListener::class);
+
+ $listener->shouldReceive('handle')->andReturnTrue();
+
+ $this->event->listen('some', "SomeListener");
+
+ $this->assertTrue($this->event->until('some'));
+ }
+
+ public function testSubscribe()
+ {
+ $listener = m::mock("overload:SomeListener", TestListener::class);
+
+ $listener->shouldReceive('subscribe')->andReturnUsing(function (Event $event) use ($listener) {
+
+ $listener->shouldReceive('onBar')->once()->andReturnFalse();
+
+ $event->listenEvents(['SomeListener::onBar' => [[$listener, 'onBar']]]);
+ });
+
+ $this->event->subscribe('SomeListener');
+
+ $this->assertTrue($this->event->hasListener('SomeListener::onBar'));
+
+ $this->event->trigger('SomeListener::onBar');
+ }
+
+ public function testAutoObserve()
+ {
+ $listener = m::mock("overload:SomeListener", TestListener::class);
+
+ $listener->shouldReceive('onBar')->once();
+
+ $this->app->shouldReceive('make')->with('SomeListener')->andReturn($listener);
+
+ $this->event->observe('SomeListener');
+
+ $this->event->trigger('bar');
+ }
+
+}
+
+class TestListener
+{
+ public function handle()
+ {
+
+ }
+
+ public function onBar()
+ {
+
+ }
+
+ public function onFoo()
+ {
+
+ }
+
+ public function subscribe()
+ {
+
+ }
+}
diff --git a/vendor/topthink/framework/tests/FilesystemTest.php b/vendor/topthink/framework/tests/FilesystemTest.php
new file mode 100644
index 000000000..df5ffe209
--- /dev/null
+++ b/vendor/topthink/framework/tests/FilesystemTest.php
@@ -0,0 +1,131 @@
+app = m::mock(App::class)->makePartial();
+ Container::setInstance($this->app);
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->config = m::mock(Config::class);
+ $this->config->shouldReceive('get')->with('filesystem.default', null)->andReturn('local');
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+ $this->filesystem = new Filesystem($this->app);
+
+ $this->root = vfsStream::setup('rootDir');
+ }
+
+ protected function tearDown(): void
+ {
+ m::close();
+ }
+
+ public function testDisk()
+ {
+ $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([
+ 'type' => 'local',
+ 'root' => $this->root->url(),
+ ]);
+
+ $this->config->shouldReceive('get')->with('filesystem.disks.foo', null)->andReturn([
+ 'type' => 'local',
+ 'root' => $this->root->url(),
+ ]);
+
+ $this->assertInstanceOf(Local::class, $this->filesystem->disk());
+
+ $this->assertInstanceOf(Local::class, $this->filesystem->disk('foo'));
+ }
+
+ public function testCache()
+ {
+ $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([
+ 'type' => 'local',
+ 'root' => $this->root->url(),
+ 'cache' => true,
+ ]);
+
+ $this->assertInstanceOf(Local::class, $this->filesystem->disk());
+
+ $this->config->shouldReceive('get')->with('filesystem.disks.cache', null)->andReturn([
+ 'type' => NullDriver::class,
+ 'root' => $this->root->url(),
+ 'cache' => [
+ 'store' => 'flysystem',
+ ],
+ ]);
+
+ $cache = m::mock(Cache::class);
+
+ $cacheDriver = m::mock(File::class);
+
+ $cache->shouldReceive('store')->once()->with('flysystem')->andReturn($cacheDriver);
+
+ $this->app->shouldReceive('make')->with(Cache::class)->andReturn($cache);
+
+ $cacheDriver->shouldReceive('get')->with('flysystem')->once()->andReturn(null);
+
+ $cacheDriver->shouldReceive('set')->withAnyArgs();
+
+ $this->filesystem->disk('cache')->put('test.txt', 'aa');
+ }
+
+ public function testPutFile()
+ {
+ $root = vfsStream::setup('rootDir', null, [
+ 'foo.jpg' => 'hello',
+ ]);
+
+ $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([
+ 'type' => NullDriver::class,
+ 'root' => $root->url(),
+ 'cache' => true,
+ ]);
+
+ $file = m::mock(\think\File::class);
+
+ $file->shouldReceive('hashName')->with(null)->once()->andReturn('foo.jpg');
+
+ $file->shouldReceive('getRealPath')->once()->andReturn($root->getChild('foo.jpg')->url());
+
+ $this->filesystem->putFile('test', $file);
+ }
+}
+
+class NullDriver extends Driver
+{
+ protected function createAdapter(): AdapterInterface
+ {
+ return new NullAdapter();
+ }
+}
diff --git a/vendor/topthink/framework/tests/HttpTest.php b/vendor/topthink/framework/tests/HttpTest.php
new file mode 100644
index 000000000..c3e0abd33
--- /dev/null
+++ b/vendor/topthink/framework/tests/HttpTest.php
@@ -0,0 +1,155 @@
+app = m::mock(App::class)->makePartial();
+
+ $this->http = m::mock(Http::class, [$this->app])->shouldAllowMockingProtectedMethods()->makePartial();
+ }
+
+ protected function prepareApp($request, $response)
+ {
+ $this->app->shouldReceive('instance')->once()->with('request', $request);
+ $this->app->shouldReceive('initialized')->once()->andReturnFalse();
+ $this->app->shouldReceive('initialize')->once();
+ $this->app->shouldReceive('get')->with('request')->andReturn($request);
+
+ $route = m::mock(Route::class);
+
+ $route->shouldReceive('dispatch')->withArgs(function ($req, $withRoute) use ($request) {
+ if ($withRoute) {
+ $withRoute();
+ }
+ return $req === $request;
+ })->andReturn($response);
+
+ $route->shouldReceive('config')->with('route_annotation')->andReturn(true);
+
+ $this->app->shouldReceive('get')->with('route')->andReturn($route);
+
+ $console = m::mock(Console::class);
+
+ $console->shouldReceive('call');
+
+ $this->app->shouldReceive('get')->with('console')->andReturn($console);
+ }
+
+ public function testRun()
+ {
+ $root = vfsStream::setup('rootDir', null, [
+ 'app' => [
+ 'controller' => [],
+ 'middleware.php' => ' [
+ 'route.php' => 'app->shouldReceive('getBasePath')->andReturn($root->getChild('app')->url() . DIRECTORY_SEPARATOR);
+ $this->app->shouldReceive('getRootPath')->andReturn($root->url() . DIRECTORY_SEPARATOR);
+
+ $request = m::mock(Request::class)->makePartial();
+ $response = m::mock(Response::class)->makePartial();
+
+ $this->prepareApp($request, $response);
+
+ $this->assertEquals($response, $this->http->run($request));
+ }
+
+ public function multiAppRunProvider()
+ {
+ $request1 = m::mock(Request::class)->makePartial();
+ $request1->shouldReceive('subDomain')->andReturn('www');
+ $request1->shouldReceive('host')->andReturn('www.domain.com');
+
+ $request2 = m::mock(Request::class)->makePartial();
+ $request2->shouldReceive('subDomain')->andReturn('app2');
+ $request2->shouldReceive('host')->andReturn('app2.domain.com');
+
+ $request3 = m::mock(Request::class)->makePartial();
+ $request3->shouldReceive('pathinfo')->andReturn('some1/a/b/c');
+
+ $request4 = m::mock(Request::class)->makePartial();
+ $request4->shouldReceive('pathinfo')->andReturn('app3/a/b/c');
+
+ $request5 = m::mock(Request::class)->makePartial();
+ $request5->shouldReceive('pathinfo')->andReturn('some2/a/b/c');
+
+ return [
+ [$request1, true, 'app1'],
+ [$request2, true, 'app2'],
+ [$request3, true, 'app3'],
+ [$request4, true, null],
+ [$request5, true, 'some2', 'path'],
+ [$request1, false, 'some3'],
+ ];
+ }
+
+ public function testRunWithException()
+ {
+ $request = m::mock(Request::class);
+ $response = m::mock(Response::class);
+
+ $this->app->shouldReceive('instance')->once()->with('request', $request);
+ $this->app->shouldReceive('initialize')->once();
+
+ $exception = new Exception();
+
+ $this->http->shouldReceive('runWithRequest')->once()->with($request)->andThrow($exception);
+
+ $handle = m::mock(Handle::class);
+
+ $handle->shouldReceive('report')->once()->with($exception);
+ $handle->shouldReceive('render')->once()->with($request, $exception)->andReturn($response);
+
+ $this->app->shouldReceive('make')->with(Handle::class)->andReturn($handle);
+
+ $this->assertEquals($response, $this->http->run($request));
+ }
+
+ public function testEnd()
+ {
+ $response = m::mock(Response::class);
+ $event = m::mock(Event::class);
+ $event->shouldReceive('trigger')->once()->with(HttpEnd::class, $response);
+ $this->app->shouldReceive('get')->once()->with('event')->andReturn($event);
+ $log = m::mock(Log::class);
+ $log->shouldReceive('save')->once();
+ $this->app->shouldReceive('get')->once()->with('log')->andReturn($log);
+
+ $this->http->end($response);
+ }
+
+}
diff --git a/vendor/topthink/framework/tests/InteractsWithApp.php b/vendor/topthink/framework/tests/InteractsWithApp.php
new file mode 100644
index 000000000..f4fcf73f7
--- /dev/null
+++ b/vendor/topthink/framework/tests/InteractsWithApp.php
@@ -0,0 +1,30 @@
+app = m::mock(App::class)->makePartial();
+ Container::setInstance($this->app);
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->app->shouldReceive('isDebug')->andReturnTrue();
+ $this->config = m::mock(Config::class)->makePartial();
+ $this->config->shouldReceive('get')->with('app.show_error_msg')->andReturnTrue();
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+ $this->app->shouldReceive('runningInConsole')->andReturn(false);
+ }
+}
diff --git a/vendor/topthink/framework/tests/LogTest.php b/vendor/topthink/framework/tests/LogTest.php
new file mode 100644
index 000000000..981110f56
--- /dev/null
+++ b/vendor/topthink/framework/tests/LogTest.php
@@ -0,0 +1,130 @@
+prepareApp();
+
+ $this->log = new Log($this->app);
+ }
+
+ public function testGetConfig()
+ {
+ $config = [
+ 'default' => 'file',
+ ];
+
+ $this->config->shouldReceive('get')->with('log')->andReturn($config);
+
+ $this->assertEquals($config, $this->log->getConfig());
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->log->getChannelConfig('foo');
+ }
+
+ public function testChannel()
+ {
+ $this->assertInstanceOf(ChannelSet::class, $this->log->channel(['file', 'mail']));
+ }
+
+ public function testLogManagerInstances()
+ {
+ $this->config->shouldReceive('get')->with("log.channels.single", null)->andReturn(['type' => 'file']);
+
+ $channel1 = $this->log->channel('single');
+ $channel2 = $this->log->channel('single');
+
+ $this->assertSame($channel1, $channel2);
+ }
+
+ public function testFileLog()
+ {
+ $root = vfsStream::setup();
+
+ $this->config->shouldReceive('get')->with("log.default", null)->andReturn('file');
+
+ $this->config->shouldReceive('get')->with("log.channels.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]);
+
+ $this->log->info('foo');
+
+ $this->assertEquals($this->log->getLog(), ['info' => ['foo']]);
+
+ $this->log->clear();
+
+ $this->assertEmpty($this->log->getLog());
+
+ $this->log->error('foo');
+ $this->assertArrayHasKey('error', $this->log->getLog());
+
+ $this->log->emergency('foo');
+ $this->assertArrayHasKey('emergency', $this->log->getLog());
+
+ $this->log->alert('foo');
+ $this->assertArrayHasKey('alert', $this->log->getLog());
+
+ $this->log->critical('foo');
+ $this->assertArrayHasKey('critical', $this->log->getLog());
+
+ $this->log->warning('foo');
+ $this->assertArrayHasKey('warning', $this->log->getLog());
+
+ $this->log->notice('foo');
+ $this->assertArrayHasKey('notice', $this->log->getLog());
+
+ $this->log->debug('foo');
+ $this->assertArrayHasKey('debug', $this->log->getLog());
+
+ $this->log->sql('foo');
+ $this->assertArrayHasKey('sql', $this->log->getLog());
+
+ $this->log->custom('foo');
+ $this->assertArrayHasKey('custom', $this->log->getLog());
+
+ $this->log->write('foo');
+ $this->assertTrue($root->hasChildren());
+ $this->assertEmpty($this->log->getLog());
+
+ $this->log->close();
+
+ $this->log->info('foo');
+
+ $this->assertEmpty($this->log->getLog());
+ }
+
+ public function testSave()
+ {
+ $root = vfsStream::setup();
+
+ $this->config->shouldReceive('get')->with("log.default", null)->andReturn('file');
+
+ $this->config->shouldReceive('get')->with("log.channels.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]);
+
+ $this->log->info('foo');
+
+ $this->log->save();
+
+ $this->assertTrue($root->hasChildren());
+ }
+
+}
diff --git a/vendor/topthink/framework/tests/MiddlewareTest.php b/vendor/topthink/framework/tests/MiddlewareTest.php
new file mode 100644
index 000000000..aa53059c0
--- /dev/null
+++ b/vendor/topthink/framework/tests/MiddlewareTest.php
@@ -0,0 +1,108 @@
+prepareApp();
+
+ $this->middleware = new Middleware($this->app);
+ }
+
+ public function testSetMiddleware()
+ {
+ $this->middleware->add('BarMiddleware', 'bar');
+
+ $this->assertEquals(1, count($this->middleware->all('bar')));
+
+ $this->middleware->controller('BarMiddleware');
+ $this->assertEquals(1, count($this->middleware->all('controller')));
+
+ $this->middleware->import(['FooMiddleware']);
+ $this->assertEquals(1, count($this->middleware->all()));
+
+ $this->middleware->unshift(['BazMiddleware', 'baz']);
+ $this->assertEquals(2, count($this->middleware->all()));
+ $this->assertEquals([['BazMiddleware', 'handle'], 'baz'], $this->middleware->all()[0]);
+
+ $this->config->shouldReceive('get')->with('middleware.alias', [])->andReturn(['foo' => ['FooMiddleware', 'FarMiddleware']]);
+
+ $this->middleware->add('foo');
+ $this->assertEquals(3, count($this->middleware->all()));
+ $this->middleware->add(function () {
+ });
+ $this->middleware->add(function () {
+ });
+ $this->assertEquals(5, count($this->middleware->all()));
+ }
+
+ public function testPipelineAndEnd()
+ {
+ $bar = m::mock("overload:BarMiddleware");
+ $foo = m::mock("overload:FooMiddleware", Foo::class);
+
+ $request = m::mock(Request::class);
+ $response = m::mock(Response::class);
+
+ $e = new Exception();
+
+ $handle = m::mock(Handle::class);
+ $handle->shouldReceive('report')->with($e)->andReturnNull();
+ $handle->shouldReceive('render')->with($request, $e)->andReturn($response);
+
+ $foo->shouldReceive('handle')->once()->andReturnUsing(function ($request, $next) {
+ return $next($request);
+ });
+ $bar->shouldReceive('handle')->once()->andReturnUsing(function ($request, $next) use ($e) {
+ $next($request);
+ throw $e;
+ });
+
+ $foo->shouldReceive('end')->once()->with($response)->andReturnNull();
+
+ $this->app->shouldReceive('make')->with(Handle::class)->andReturn($handle);
+
+ $this->config->shouldReceive('get')->once()->with('middleware.priority', [])->andReturn(['FooMiddleware', 'BarMiddleware']);
+
+ $this->middleware->import([function ($request, $next) {
+ return $next($request);
+ }, 'BarMiddleware', 'FooMiddleware']);
+
+ $this->assertInstanceOf(Pipeline::class, $pipeline = $this->middleware->pipeline());
+
+ $pipeline->send($request)->then(function ($request) use ($e, $response) {
+ throw $e;
+ });
+
+ $this->middleware->end($response);
+ }
+}
+
+class Foo
+{
+ public function end(Response $response)
+ {
+ }
+}
diff --git a/vendor/topthink/framework/tests/RouteTest.php b/vendor/topthink/framework/tests/RouteTest.php
new file mode 100644
index 000000000..e992d0fed
--- /dev/null
+++ b/vendor/topthink/framework/tests/RouteTest.php
@@ -0,0 +1,286 @@
+prepareApp();
+ $this->route = new Route($this->app);
+ }
+
+ /**
+ * @param $path
+ * @param string $method
+ * @param string $host
+ * @return m\Mock|Request
+ */
+ protected function makeRequest($path, $method = 'GET', $host = 'localhost')
+ {
+ $request = m::mock(Request::class)->makePartial();
+ $request->shouldReceive('host')->andReturn($host);
+ $request->shouldReceive('pathinfo')->andReturn($path);
+ $request->shouldReceive('url')->andReturn('/' . $path);
+ $request->shouldReceive('method')->andReturn(strtoupper($method));
+ return $request;
+ }
+
+ public function testSimpleRequest()
+ {
+ $this->route->get('foo', function () {
+ return 'get-foo';
+ });
+
+ $this->route->put('foo', function () {
+ return 'put-foo';
+ });
+
+ $this->route->group(function () {
+ $this->route->post('foo', function () {
+ return 'post-foo';
+ });
+ });
+
+ $request = $this->makeRequest('foo', 'post');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(200, $response->getCode());
+ $this->assertEquals('post-foo', $response->getContent());
+
+ $request = $this->makeRequest('foo', 'get');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(200, $response->getCode());
+ $this->assertEquals('get-foo', $response->getContent());
+ }
+
+ public function testOptionsRequest()
+ {
+ $this->route->get('foo', function () {
+ return 'get-foo';
+ });
+
+ $this->route->put('foo', function () {
+ return 'put-foo';
+ });
+
+ $this->route->group(function () {
+ $this->route->post('foo', function () {
+ return 'post-foo';
+ });
+ });
+ $this->route->group('abc', function () {
+ $this->route->post('foo/:id', function () {
+ return 'post-abc-foo';
+ });
+ });
+
+ $this->route->post('foo/:id', function () {
+ return 'post-abc-foo';
+ });
+
+ $this->route->resource('bar', 'SomeClass');
+
+ $request = $this->makeRequest('foo', 'options');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(204, $response->getCode());
+ $this->assertEquals('GET, PUT, POST', $response->getHeader('Allow'));
+
+ $request = $this->makeRequest('bar', 'options');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(204, $response->getCode());
+ $this->assertEquals('GET, POST', $response->getHeader('Allow'));
+
+ $request = $this->makeRequest('bar/1', 'options');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(204, $response->getCode());
+ $this->assertEquals('GET, PUT, DELETE', $response->getHeader('Allow'));
+
+ $request = $this->makeRequest('xxxx', 'options');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(204, $response->getCode());
+ $this->assertEquals('GET, POST, PUT, DELETE', $response->getHeader('Allow'));
+ }
+
+ public function testAllowCrossDomain()
+ {
+ $this->route->get('foo', function () {
+ return 'get-foo';
+ })->allowCrossDomain(['some' => 'bar']);
+
+ $request = $this->makeRequest('foo', 'get');
+ $response = $this->route->dispatch($request);
+
+ $this->assertEquals('bar', $response->getHeader('some'));
+ $this->assertArrayHasKey('Access-Control-Allow-Credentials', $response->getHeader());
+
+ $request = $this->makeRequest('foo2', 'options');
+ $response = $this->route->dispatch($request);
+
+ $this->assertEquals(204, $response->getCode());
+ $this->assertArrayHasKey('Access-Control-Allow-Credentials', $response->getHeader());
+ $this->assertEquals('GET, POST, PUT, DELETE', $response->getHeader('Allow'));
+ }
+
+ public function testControllerDispatch()
+ {
+ $this->route->get('foo', 'foo/bar');
+
+ $controller = m::Mock(\stdClass::class);
+
+ $this->app->shouldReceive('parseClass')->with('controller', 'Foo')->andReturn($controller->mockery_getName());
+ $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller);
+
+ $controller->shouldReceive('bar')->andReturn('bar');
+
+ $request = $this->makeRequest('foo');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals('bar', $response->getContent());
+ }
+
+ public function testEmptyControllerDispatch()
+ {
+ $this->route->get('foo', 'foo/bar');
+
+ $controller = m::Mock(\stdClass::class);
+
+ $this->app->shouldReceive('parseClass')->with('controller', 'Error')->andReturn($controller->mockery_getName());
+ $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller);
+
+ $controller->shouldReceive('bar')->andReturn('bar');
+
+ $request = $this->makeRequest('foo');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals('bar', $response->getContent());
+ }
+
+ protected function createMiddleware($times = 1)
+ {
+ $middleware = m::mock(Str::random(5));
+ $middleware->shouldReceive('handle')->times($times)->andReturnUsing(function ($request, Closure $next) {
+ return $next($request);
+ });
+ $this->app->shouldReceive('make')->with($middleware->mockery_getName())->andReturn($middleware);
+
+ return $middleware;
+ }
+
+ public function testControllerWithMiddleware()
+ {
+ $this->route->get('foo', 'foo/bar');
+
+ $controller = m::mock(FooClass::class);
+
+ $controller->middleware = [
+ $this->createMiddleware()->mockery_getName() . ":params1:params2",
+ $this->createMiddleware(0)->mockery_getName() => ['except' => 'bar'],
+ $this->createMiddleware()->mockery_getName() => ['only' => 'bar'],
+ ];
+
+ $this->app->shouldReceive('parseClass')->with('controller', 'Foo')->andReturn($controller->mockery_getName());
+ $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller);
+
+ $controller->shouldReceive('bar')->once()->andReturn('bar');
+
+ $request = $this->makeRequest('foo');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals('bar', $response->getContent());
+ }
+
+ public function testUrlDispatch()
+ {
+ $controller = m::mock(FooClass::class);
+ $controller->shouldReceive('index')->andReturn('bar');
+
+ $this->app->shouldReceive('parseClass')->once()->with('controller', 'Foo')->andReturn($controller->mockery_getName());
+ $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller);
+
+ $request = $this->makeRequest('foo');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals('bar', $response->getContent());
+ }
+
+ public function testRedirectDispatch()
+ {
+ $this->route->redirect('foo', 'http://localhost', 302);
+
+ $request = $this->makeRequest('foo');
+ $this->app->shouldReceive('make')->with(Request::class)->andReturn($request);
+ $response = $this->route->dispatch($request);
+
+ $this->assertInstanceOf(Redirect::class, $response);
+ $this->assertEquals(302, $response->getCode());
+ $this->assertEquals('http://localhost', $response->getData());
+ }
+
+ public function testViewDispatch()
+ {
+ $this->route->view('foo', 'index/hello', ['city' => 'shanghai']);
+
+ $request = $this->makeRequest('foo');
+ $response = $this->route->dispatch($request);
+
+ $this->assertInstanceOf(View::class, $response);
+ $this->assertEquals(['city' => 'shanghai'], $response->getVars());
+ $this->assertEquals('index/hello', $response->getData());
+ }
+
+ public function testResponseDispatch()
+ {
+ $this->route->get('hello/:name', response()
+ ->data('Hello,ThinkPHP')
+ ->code(200)
+ ->contentType('text/plain'));
+
+ $request = $this->makeRequest('hello/some');
+ $response = $this->route->dispatch($request);
+
+ $this->assertEquals('Hello,ThinkPHP', $response->getContent());
+ $this->assertEquals(200, $response->getCode());
+ }
+
+ public function testDomainBindResponse()
+ {
+ $this->route->domain('test', function () {
+ $this->route->get('/', function () {
+ return 'Hello,ThinkPHP';
+ });
+ });
+
+ $request = $this->makeRequest('', 'get', 'test.domain.com');
+ $response = $this->route->dispatch($request);
+
+ $this->assertEquals('Hello,ThinkPHP', $response->getContent());
+ $this->assertEquals(200, $response->getCode());
+ }
+
+}
+
+class FooClass
+{
+ public $middleware = [];
+
+ public function bar()
+ {
+
+ }
+}
diff --git a/vendor/topthink/framework/tests/SessionTest.php b/vendor/topthink/framework/tests/SessionTest.php
new file mode 100644
index 000000000..b3b48a70d
--- /dev/null
+++ b/vendor/topthink/framework/tests/SessionTest.php
@@ -0,0 +1,225 @@
+app = m::mock(App::class)->makePartial();
+ Container::setInstance($this->app);
+
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->config = m::mock(Config::class)->makePartial();
+
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+ $handlerClass = "\\think\\session\\driver\\Test" . Str::random(10);
+ $this->config->shouldReceive("get")->with("session.type", "file")->andReturn($handlerClass);
+ $this->session = new Session($this->app);
+
+ $this->handler = m::mock('overload:' . $handlerClass, SessionHandlerInterface::class);
+ }
+
+ public function testLoadData()
+ {
+ $data = [
+ "bar" => 'foo',
+ ];
+
+ $id = md5(uniqid());
+
+ $this->handler->shouldReceive("read")->once()->with($id)->andReturn(serialize($data));
+
+ $this->session->setId($id);
+ $this->session->init();
+
+ $this->assertEquals('foo', $this->session->get('bar'));
+ $this->assertTrue($this->session->has('bar'));
+ $this->assertFalse($this->session->has('foo'));
+
+ $this->session->set('foo', 'bar');
+ $this->assertTrue($this->session->has('foo'));
+
+ $this->assertEquals('bar', $this->session->pull('foo'));
+ $this->assertFalse($this->session->has('foo'));
+ }
+
+ public function testSave()
+ {
+
+ $id = md5(uniqid());
+
+ $this->handler->shouldReceive('read')->once()->with($id)->andReturn("");
+
+ $this->handler->shouldReceive('write')->once()->with($id, serialize([
+ "bar" => 'foo',
+ ]))->andReturnTrue();
+
+ $this->session->setId($id);
+ $this->session->init();
+
+ $this->session->set('bar', 'foo');
+
+ $this->session->save();
+ }
+
+ public function testFlash()
+ {
+ $this->session->flash('foo', 'bar');
+ $this->session->flash('bar', 0);
+ $this->session->flash('baz', true);
+
+ $this->assertTrue($this->session->has('foo'));
+ $this->assertEquals('bar', $this->session->get('foo'));
+ $this->assertEquals(0, $this->session->get('bar'));
+ $this->assertTrue($this->session->get('baz'));
+
+ $this->session->clearFlashData();
+
+ $this->assertTrue($this->session->has('foo'));
+ $this->assertEquals('bar', $this->session->get('foo'));
+ $this->assertEquals(0, $this->session->get('bar'));
+
+ $this->session->clearFlashData();
+
+ $this->assertFalse($this->session->has('foo'));
+ $this->assertNull($this->session->get('foo'));
+
+ $this->session->flash('foo', 'bar');
+ $this->assertTrue($this->session->has('foo'));
+ $this->session->clearFlashData();
+ $this->session->reflash();
+ $this->session->clearFlashData();
+
+ $this->assertTrue($this->session->has('foo'));
+ }
+
+ public function testClear()
+ {
+ $this->session->set('bar', 'foo');
+ $this->assertEquals('foo', $this->session->get('bar'));
+ $this->session->clear();
+ $this->assertFalse($this->session->has('foo'));
+ }
+
+ public function testSetName()
+ {
+ $this->session->setName('foo');
+ $this->assertEquals('foo', $this->session->getName());
+ }
+
+ public function testDestroy()
+ {
+ $id = md5(uniqid());
+
+ $this->handler->shouldReceive('read')->once()->with($id)->andReturn("");
+ $this->handler->shouldReceive('delete')->once()->with($id)->andReturnTrue();
+
+ $this->session->setId($id);
+ $this->session->init();
+
+ $this->session->set('bar', 'foo');
+
+ $this->session->destroy();
+
+ $this->assertFalse($this->session->has('bar'));
+
+ $this->assertNotEquals($id, $this->session->getId());
+ }
+
+ public function testFileHandler()
+ {
+ $root = vfsStream::setup();
+
+ vfsStream::newFile('bar')
+ ->at($root)
+ ->lastModified(time());
+
+ vfsStream::newFile('bar')
+ ->at(vfsStream::newDirectory("foo")->at($root))
+ ->lastModified(100);
+
+ $this->assertTrue($root->hasChild("bar"));
+ $this->assertTrue($root->hasChild("foo/bar"));
+
+ $handler = new TestFileHandle($this->app, [
+ 'path' => $root->url(),
+ 'gc_probability' => 1,
+ 'gc_divisor' => 1,
+ ]);
+
+ $this->assertTrue($root->hasChild("bar"));
+ $this->assertFalse($root->hasChild("foo/bar"));
+
+ $id = md5(uniqid());
+ $handler->write($id, "bar");
+
+ $this->assertTrue($root->hasChild("sess_{$id}"));
+
+ $this->assertEquals("bar", $handler->read($id));
+
+ $handler->delete($id);
+
+ $this->assertFalse($root->hasChild("sess_{$id}"));
+ }
+
+ public function testCacheHandler()
+ {
+ $id = md5(uniqid());
+
+ $cache = m::mock(\think\Cache::class);
+
+ $store = m::mock(Driver::class);
+
+ $cache->shouldReceive('store')->once()->with('redis')->andReturn($store);
+
+ $handler = new Cache($cache, ['store' => 'redis']);
+
+ $store->shouldReceive("set")->with($id, "bar", 1440)->once()->andReturnTrue();
+ $handler->write($id, "bar");
+
+ $store->shouldReceive("get")->with($id)->once()->andReturn("bar");
+ $this->assertEquals("bar", $handler->read($id));
+
+ $store->shouldReceive("delete")->with($id)->once()->andReturnTrue();
+ $handler->delete($id);
+ }
+}
+
+class TestFileHandle extends File
+{
+ protected function writeFile($path, $content): bool
+ {
+ return (bool) file_put_contents($path, $content);
+ }
+}
diff --git a/vendor/topthink/framework/tests/ViewTest.php b/vendor/topthink/framework/tests/ViewTest.php
new file mode 100644
index 000000000..e4135109a
--- /dev/null
+++ b/vendor/topthink/framework/tests/ViewTest.php
@@ -0,0 +1,127 @@
+app = m::mock(App::class)->makePartial();
+ Container::setInstance($this->app);
+
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->config = m::mock(Config::class)->makePartial();
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+
+ $this->view = new View($this->app);
+ }
+
+ public function testAssignData()
+ {
+ $this->view->assign('foo', 'bar');
+ $this->view->assign(['baz' => 'boom']);
+ $this->view->qux = "corge";
+
+ $this->assertEquals('bar', $this->view->foo);
+ $this->assertEquals('boom', $this->view->baz);
+ $this->assertEquals('corge', $this->view->qux);
+ $this->assertTrue(isset($this->view->qux));
+ }
+
+ public function testRender()
+ {
+ $this->config->shouldReceive("get")->with("view.type", 'php')->andReturn(TestTemplate::class);
+
+ $this->view->filter(function ($content) {
+ return $content;
+ });
+
+ $this->assertEquals("fetch", $this->view->fetch('foo'));
+ $this->assertEquals("display", $this->view->display('foo'));
+ }
+
+}
+
+class TestTemplate implements TemplateHandlerInterface
+{
+
+ /**
+ * 检测是否存在模板文件
+ * @access public
+ * @param string $template 模板文件或者模板规则
+ * @return bool
+ */
+ public function exists(string $template): bool
+ {
+ return true;
+ }
+
+ /**
+ * 渲染模板文件
+ * @access public
+ * @param string $template 模板文件
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function fetch(string $template, array $data = []): void
+ {
+ echo "fetch";
+ }
+
+ /**
+ * 渲染模板内容
+ * @access public
+ * @param string $content 模板内容
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function display(string $content, array $data = []): void
+ {
+ echo "display";
+ }
+
+ /**
+ * 配置模板引擎
+ * @access private
+ * @param array $config 参数
+ * @return void
+ */
+ public function config(array $config): void
+ {
+ // TODO: Implement config() method.
+ }
+
+ /**
+ * 获取模板引擎配置
+ * @access public
+ * @param string $name 参数名
+ * @return void
+ */
+ public function getConfig(string $name)
+ {
+ // TODO: Implement getConfig() method.
+ }
+}
diff --git a/vendor/topthink/framework/tests/bootstrap.php b/vendor/topthink/framework/tests/bootstrap.php
new file mode 100644
index 000000000..34590612e
--- /dev/null
+++ b/vendor/topthink/framework/tests/bootstrap.php
@@ -0,0 +1,3 @@
+ 以下类库都在`\\think\\helper`命名空间下
+
+## Str
+
+> 字符串操作
+
+```
+// 检查字符串中是否包含某些字符串
+Str::contains($haystack, $needles)
+
+// 检查字符串是否以某些字符串结尾
+Str::endsWith($haystack, $needles)
+
+// 获取指定长度的随机字母数字组合的字符串
+Str::random($length = 16)
+
+// 字符串转小写
+Str::lower($value)
+
+// 字符串转大写
+Str::upper($value)
+
+// 获取字符串的长度
+Str::length($value)
+
+// 截取字符串
+Str::substr($string, $start, $length = null)
+
+```
\ No newline at end of file
diff --git a/vendor/topthink/think-helper/composer.json b/vendor/topthink/think-helper/composer.json
new file mode 100644
index 000000000..b68c43b51
--- /dev/null
+++ b/vendor/topthink/think-helper/composer.json
@@ -0,0 +1,22 @@
+{
+ "name": "topthink/think-helper",
+ "description": "The ThinkPHP6 Helper Package",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "yunwuxin",
+ "email": "448901948@qq.com"
+ }
+ ],
+ "require": {
+ "php": ">=7.1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "think\\": "src"
+ },
+ "files": [
+ "src/helper.php"
+ ]
+ }
+}
diff --git a/thinkphp/library/think/Collection.php b/vendor/topthink/think-helper/src/Collection.php
old mode 100755
new mode 100644
similarity index 64%
rename from thinkphp/library/think/Collection.php
rename to vendor/topthink/think-helper/src/Collection.php
index d58c8999b..fa408c25a
--- a/thinkphp/library/think/Collection.php
+++ b/vendor/topthink/think-helper/src/Collection.php
@@ -2,12 +2,13 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: zhangyajun <448901948@qq.com>
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think;
@@ -16,8 +17,14 @@ use ArrayIterator;
use Countable;
use IteratorAggregate;
use JsonSerializable;
+use think\contract\Arrayable;
+use think\contract\Jsonable;
+use think\helper\Arr;
-class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable
+/**
+ * 数据集管理类
+ */
+class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Arrayable, Jsonable
{
/**
* 数据集数据
@@ -40,19 +47,19 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
* @access public
* @return bool
*/
- public function isEmpty()
+ public function isEmpty(): bool
{
return empty($this->items);
}
- public function toArray()
+ public function toArray(): array
{
return array_map(function ($value) {
- return ($value instanceof Model || $value instanceof self) ? $value->toArray() : $value;
+ return $value instanceof Arrayable ? $value->toArray() : $value;
}, $this->items);
}
- public function all()
+ public function all(): array
{
return $this->items;
}
@@ -61,7 +68,7 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
* 合并数组
*
* @access public
- * @param mixed $items
+ * @param mixed $items 数据
* @return static
*/
public function merge($items)
@@ -69,28 +76,17 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
return new static(array_merge($this->items, $this->convertToArray($items)));
}
- /**
- * 交换数组中的键和值
- *
- * @access public
- * @return static
- */
- public function flip()
- {
- return new static(array_flip($this->items));
- }
-
/**
* 按指定键整理数据
*
* @access public
- * @param mixed $items 数据
- * @param string $indexKey 键名
+ * @param mixed $items 数据
+ * @param string $indexKey 键名
* @return array
*/
- public function dictionary($items = null, &$indexKey = null)
+ public function dictionary($items = null, string &$indexKey = null)
{
- if ($items instanceof self || $items instanceof Paginator) {
+ if ($items instanceof self) {
$items = $items->all();
}
@@ -111,11 +107,11 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
* 比较数组,返回差集
*
* @access public
- * @param mixed $items 数据
- * @param string $indexKey 指定比较的键名
+ * @param mixed $items 数据
+ * @param string $indexKey 指定比较的键名
* @return static
*/
- public function diff($items, $indexKey = null)
+ public function diff($items, string $indexKey = null)
{
if ($this->isEmpty() || is_scalar($this->items[0])) {
return new static(array_diff($this->items, $this->convertToArray($items)));
@@ -139,11 +135,11 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
* 比较数组,返回交集
*
* @access public
- * @param mixed $items 数据
- * @param string $indexKey 指定比较的键名
+ * @param mixed $items 数据
+ * @param string $indexKey 指定比较的键名
* @return static
*/
- public function intersect($items, $indexKey = null)
+ public function intersect($items, string $indexKey = null)
{
if ($this->isEmpty() || is_scalar($this->items[0])) {
return new static(array_diff($this->items, $this->convertToArray($items)));
@@ -163,25 +159,36 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
return new static($intersect);
}
+ /**
+ * 交换数组中的键和值
+ *
+ * @access public
+ * @return static
+ */
+ public function flip()
+ {
+ return new static(array_flip($this->items));
+ }
+
/**
* 返回数组中所有的键名
*
* @access public
- * @return array
+ * @return static
*/
public function keys()
{
- $current = current($this->items);
+ return new static(array_keys($this->items));
+ }
- if (is_scalar($current)) {
- $array = $this->items;
- } elseif (is_array($current)) {
- $array = $current;
- } else {
- $array = $current->toArray();
- }
-
- return array_keys($array);
+ /**
+ * 返回数组中所有的值组成的新 Collection 实例
+ * @access public
+ * @return static
+ */
+ public function values()
+ {
+ return new static(array_values($this->items));
}
/**
@@ -199,8 +206,8 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
* 通过使用用户自定义函数,以字符串返回数组
*
* @access public
- * @param callable $callback
- * @param mixed $initial
+ * @param callable $callback 调用方法
+ * @param mixed $initial
* @return mixed
*/
public function reduce(callable $callback, $initial = null)
@@ -233,28 +240,30 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
/**
* 在数组结尾插入一个元素
* @access public
- * @param mixed $value
- * @param mixed $key
- * @return void
+ * @param mixed $value 元素
+ * @param string $key KEY
+ * @return $this
*/
- public function push($value, $key = null)
+ public function push($value, string $key = null)
{
if (is_null($key)) {
$this->items[] = $value;
} else {
$this->items[$key] = $value;
}
+
+ return $this;
}
/**
* 把一个数组分割为新的数组块.
*
* @access public
- * @param int $size
- * @param bool $preserveKeys
+ * @param int $size 块大小
+ * @param bool $preserveKeys
* @return static
*/
- public function chunk($size, $preserveKeys = false)
+ public function chunk(int $size, bool $preserveKeys = false)
{
$chunks = [];
@@ -268,24 +277,26 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
/**
* 在数组开头插入一个元素
* @access public
- * @param mixed $value
- * @param mixed $key
- * @return void
+ * @param mixed $value 元素
+ * @param string $key KEY
+ * @return $this
*/
- public function unshift($value, $key = null)
+ public function unshift($value, string $key = null)
{
if (is_null($key)) {
array_unshift($this->items, $value);
} else {
$this->items = [$key => $value] + $this->items;
}
+
+ return $this;
}
/**
* 给每个元素执行个回调
*
* @access public
- * @param callable $callback
+ * @param callable $callback 回调
* @return $this
*/
public function each(callable $callback)
@@ -306,7 +317,7 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
/**
* 用回调函数处理数组中的元素
* @access public
- * @param callable|null $callback
+ * @param callable|null $callback 回调
* @return static
*/
public function map(callable $callback)
@@ -317,7 +328,7 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
/**
* 用回调函数过滤数组中的元素
* @access public
- * @param callable|null $callback
+ * @param callable|null $callback 回调
* @return static
*/
public function filter(callable $callback = null)
@@ -332,12 +343,12 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
/**
* 根据字段条件过滤数组中的元素
* @access public
- * @param string $field 字段名
- * @param mixed $operator 操作符
- * @param mixed $value 数据
+ * @param string $field 字段名
+ * @param mixed $operator 操作符
+ * @param mixed $value 数据
* @return static
*/
- public function where($field, $operator, $value = null)
+ public function where(string $field, $operator, $value = null)
{
if (is_null($value)) {
$value = $operator;
@@ -346,14 +357,14 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
return $this->filter(function ($data) use ($field, $operator, $value) {
if (strpos($field, '.')) {
- list($field, $relation) = explode('.', $field);
+ [$field, $relation] = explode('.', $field);
- $result = isset($data[$field][$relation]) ? $data[$field][$relation] : null;
+ $result = $data[$field][$relation] ?? null;
} else {
- $result = isset($data[$field]) ? $data[$field] : null;
+ $result = $data[$field] ?? null;
}
- switch ($operator) {
+ switch (strtolower($operator)) {
case '===':
return $result === $value;
case '!==':
@@ -378,10 +389,10 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
case 'not in':
return is_scalar($result) && !in_array($result, $value, true);
case 'between':
- list($min, $max) = is_string($value) ? explode(',', $value) : $value;
+ [$min, $max] = is_string($value) ? explode(',', $value) : $value;
return is_scalar($result) && $result >= $min && $result <= $max;
case 'not between':
- list($min, $max) = is_string($value) ? explode(',', $value) : $value;
+ [$min, $max] = is_string($value) ? explode(',', $value) : $value;
return is_scalar($result) && $result > $max || $result < $min;
case '==':
case '=':
@@ -391,14 +402,86 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
});
}
+ /**
+ * LIKE过滤
+ * @access public
+ * @param string $field 字段名
+ * @param string $value 数据
+ * @return static
+ */
+ public function whereLike(string $field, string $value)
+ {
+ return $this->where($field, 'like', $value);
+ }
+
+ /**
+ * NOT LIKE过滤
+ * @access public
+ * @param string $field 字段名
+ * @param string $value 数据
+ * @return static
+ */
+ public function whereNotLike(string $field, string $value)
+ {
+ return $this->where($field, 'not like', $value);
+ }
+
+ /**
+ * IN过滤
+ * @access public
+ * @param string $field 字段名
+ * @param array $value 数据
+ * @return static
+ */
+ public function whereIn(string $field, array $value)
+ {
+ return $this->where($field, 'in', $value);
+ }
+
+ /**
+ * NOT IN过滤
+ * @access public
+ * @param string $field 字段名
+ * @param array $value 数据
+ * @return static
+ */
+ public function whereNotIn(string $field, array $value)
+ {
+ return $this->where($field, 'not in', $value);
+ }
+
+ /**
+ * BETWEEN 过滤
+ * @access public
+ * @param string $field 字段名
+ * @param mixed $value 数据
+ * @return static
+ */
+ public function whereBetween(string $field, $value)
+ {
+ return $this->where($field, 'between', $value);
+ }
+
+ /**
+ * NOT BETWEEN 过滤
+ * @access public
+ * @param string $field 字段名
+ * @param mixed $value 数据
+ * @return static
+ */
+ public function whereNotBetween(string $field, $value)
+ {
+ return $this->where($field, 'not between', $value);
+ }
+
/**
* 返回数据中指定的一列
* @access public
- * @param mixed $columnKey 键名
- * @param mixed $indexKey 作为索引值的列
+ * @param string|null $columnKey 键名
+ * @param string|null $indexKey 作为索引值的列
* @return array
*/
- public function column($columnKey, $indexKey = null)
+ public function column( ? string $columnKey, string $indexKey = null)
{
return array_column($this->items, $columnKey, $indexKey);
}
@@ -407,7 +490,7 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
* 对数组排序
*
* @access public
- * @param callable|null $callback
+ * @param callable|null $callback 回调
* @return static
*/
public function sort(callable $callback = null)
@@ -416,7 +499,6 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
$callback = $callback ?: function ($a, $b) {
return $a == $b ? 0 : (($a < $b) ? -1 : 1);
-
};
uasort($items, $callback);
@@ -427,22 +509,17 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
/**
* 指定字段排序
* @access public
- * @param string $field 排序字段
- * @param string $order 排序
- * @param bool $intSort 是否为数字排序
+ * @param string $field 排序字段
+ * @param string $order 排序
* @return $this
*/
- public function order($field, $order = null, $intSort = true)
+ public function order(string $field, string $order = 'asc')
{
- return $this->sort(function ($a, $b) use ($field, $order, $intSort) {
- $fieldA = isset($a[$field]) ? $a[$field] : null;
- $fieldB = isset($b[$field]) ? $b[$field] : null;
+ return $this->sort(function ($a, $b) use ($field, $order) {
+ $fieldA = $a[$field] ?? null;
+ $fieldB = $b[$field] ?? null;
- if ($intSort) {
- return 'desc' == strtolower($order) ? $fieldB >= $fieldA : $fieldA >= $fieldB;
- } else {
- return 'desc' == strtolower($order) ? strcmp($fieldB, $fieldA) : strcmp($fieldA, $fieldB);
- }
+ return 'desc' == strtolower($order) ? intval($fieldB > $fieldA) : intval($fieldA > $fieldB);
});
}
@@ -461,16 +538,42 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
return new static($items);
}
+ /**
+ * 获取第一个单元数据
+ *
+ * @access public
+ * @param callable|null $callback
+ * @param null $default
+ * @return mixed
+ */
+ public function first(callable $callback = null, $default = null)
+ {
+ return Arr::first($this->items, $callback, $default);
+ }
+
+ /**
+ * 获取最后一个单元数据
+ *
+ * @access public
+ * @param callable|null $callback
+ * @param null $default
+ * @return mixed
+ */
+ public function last(callable $callback = null, $default = null)
+ {
+ return Arr::last($this->items, $callback, $default);
+ }
+
/**
* 截取数组
*
* @access public
- * @param int $offset
- * @param int $length
- * @param bool $preserveKeys
+ * @param int $offset 起始位置
+ * @param int $length 截取长度
+ * @param bool $preserveKeys preserveKeys
* @return static
*/
- public function slice($offset, $length = null, $preserveKeys = false)
+ public function slice(int $offset, int $length = null, bool $preserveKeys = false)
{
return new static(array_slice($this->items, $offset, $length, $preserveKeys));
}
@@ -521,10 +624,10 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
/**
* 转换当前数据集为JSON字符串
* @access public
- * @param integer $options json参数
+ * @param integer $options json参数
* @return string
*/
- public function toJson($options = JSON_UNESCAPED_UNICODE)
+ public function toJson(int $options = JSON_UNESCAPED_UNICODE) : string
{
return json_encode($this->toArray(), $options);
}
@@ -538,10 +641,10 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
* 转换成数组
*
* @access public
- * @param mixed $items
+ * @param mixed $items 数据
* @return array
*/
- protected function convertToArray($items)
+ protected function convertToArray($items): array
{
if ($items instanceof self) {
return $items->all();
diff --git a/vendor/topthink/think-helper/src/contract/Arrayable.php b/vendor/topthink/think-helper/src/contract/Arrayable.php
new file mode 100644
index 000000000..7c6b992b0
--- /dev/null
+++ b/vendor/topthink/think-helper/src/contract/Arrayable.php
@@ -0,0 +1,8 @@
+
+// +----------------------------------------------------------------------
+
+use think\Collection;
+use think\helper\Arr;
+
+if (!function_exists('throw_if')) {
+ /**
+ * 按条件抛异常
+ *
+ * @param mixed $condition
+ * @param Throwable|string $exception
+ * @param array ...$parameters
+ * @return mixed
+ *
+ * @throws Throwable
+ */
+ function throw_if($condition, $exception, ...$parameters)
+ {
+ if ($condition) {
+ throw (is_string($exception) ? new $exception(...$parameters) : $exception);
+ }
+
+ return $condition;
+ }
+}
+
+if (!function_exists('throw_unless')) {
+ /**
+ * 按条件抛异常
+ *
+ * @param mixed $condition
+ * @param Throwable|string $exception
+ * @param array ...$parameters
+ * @return mixed
+ * @throws Throwable
+ */
+ function throw_unless($condition, $exception, ...$parameters)
+ {
+ if (!$condition) {
+ throw (is_string($exception) ? new $exception(...$parameters) : $exception);
+ }
+
+ return $condition;
+ }
+}
+
+if (!function_exists('tap')) {
+ /**
+ * 对一个值调用给定的闭包,然后返回该值
+ *
+ * @param mixed $value
+ * @param callable|null $callback
+ * @return mixed
+ */
+ function tap($value, $callback = null)
+ {
+ if (is_null($callback)) {
+ return $value;
+ }
+
+ $callback($value);
+
+ return $value;
+ }
+}
+
+if (!function_exists('value')) {
+ /**
+ * Return the default value of the given value.
+ *
+ * @param mixed $value
+ * @return mixed
+ */
+ function value($value)
+ {
+ return $value instanceof Closure ? $value() : $value;
+ }
+}
+
+if (!function_exists('collect')) {
+ /**
+ * Create a collection from the given value.
+ *
+ * @param mixed $value
+ * @return Collection
+ */
+ function collect($value = null)
+ {
+ return new Collection($value);
+ }
+}
+
+if (!function_exists('data_fill')) {
+ /**
+ * Fill in data where it's missing.
+ *
+ * @param mixed $target
+ * @param string|array $key
+ * @param mixed $value
+ * @return mixed
+ */
+ function data_fill(&$target, $key, $value)
+ {
+ return data_set($target, $key, $value, false);
+ }
+}
+
+if (!function_exists('data_get')) {
+ /**
+ * Get an item from an array or object using "dot" notation.
+ *
+ * @param mixed $target
+ * @param string|array|int $key
+ * @param mixed $default
+ * @return mixed
+ */
+ function data_get($target, $key, $default = null)
+ {
+ if (is_null($key)) {
+ return $target;
+ }
+
+ $key = is_array($key) ? $key : explode('.', $key);
+
+ while (!is_null($segment = array_shift($key))) {
+ if ('*' === $segment) {
+ if ($target instanceof Collection) {
+ $target = $target->all();
+ } elseif (!is_array($target)) {
+ return value($default);
+ }
+
+ $result = [];
+
+ foreach ($target as $item) {
+ $result[] = data_get($item, $key);
+ }
+
+ return in_array('*', $key) ? Arr::collapse($result) : $result;
+ }
+
+ if (Arr::accessible($target) && Arr::exists($target, $segment)) {
+ $target = $target[$segment];
+ } elseif (is_object($target) && isset($target->{$segment})) {
+ $target = $target->{$segment};
+ } else {
+ return value($default);
+ }
+ }
+
+ return $target;
+ }
+}
+
+if (!function_exists('data_set')) {
+ /**
+ * Set an item on an array or object using dot notation.
+ *
+ * @param mixed $target
+ * @param string|array $key
+ * @param mixed $value
+ * @param bool $overwrite
+ * @return mixed
+ */
+ function data_set(&$target, $key, $value, $overwrite = true)
+ {
+ $segments = is_array($key) ? $key : explode('.', $key);
+
+ if (($segment = array_shift($segments)) === '*') {
+ if (!Arr::accessible($target)) {
+ $target = [];
+ }
+
+ if ($segments) {
+ foreach ($target as &$inner) {
+ data_set($inner, $segments, $value, $overwrite);
+ }
+ } elseif ($overwrite) {
+ foreach ($target as &$inner) {
+ $inner = $value;
+ }
+ }
+ } elseif (Arr::accessible($target)) {
+ if ($segments) {
+ if (!Arr::exists($target, $segment)) {
+ $target[$segment] = [];
+ }
+
+ data_set($target[$segment], $segments, $value, $overwrite);
+ } elseif ($overwrite || !Arr::exists($target, $segment)) {
+ $target[$segment] = $value;
+ }
+ } elseif (is_object($target)) {
+ if ($segments) {
+ if (!isset($target->{$segment})) {
+ $target->{$segment} = [];
+ }
+
+ data_set($target->{$segment}, $segments, $value, $overwrite);
+ } elseif ($overwrite || !isset($target->{$segment})) {
+ $target->{$segment} = $value;
+ }
+ } else {
+ $target = [];
+
+ if ($segments) {
+ data_set($target[$segment], $segments, $value, $overwrite);
+ } elseif ($overwrite) {
+ $target[$segment] = $value;
+ }
+ }
+
+ return $target;
+ }
+}
+
+if (!function_exists('trait_uses_recursive')) {
+ /**
+ * 获取一个trait里所有引用到的trait
+ *
+ * @param string $trait Trait
+ * @return array
+ */
+ function trait_uses_recursive(string $trait): array
+ {
+ $traits = class_uses($trait);
+ foreach ($traits as $trait) {
+ $traits += trait_uses_recursive($trait);
+ }
+
+ return $traits;
+ }
+}
+
+if (!function_exists('class_basename')) {
+ /**
+ * 获取类名(不包含命名空间)
+ *
+ * @param mixed $class 类名
+ * @return string
+ */
+ function class_basename($class): string
+ {
+ $class = is_object($class) ? get_class($class) : $class;
+ return basename(str_replace('\\', '/', $class));
+ }
+}
+
+if (!function_exists('class_uses_recursive')) {
+ /**
+ *获取一个类里所有用到的trait,包括父类的
+ *
+ * @param mixed $class 类名
+ * @return array
+ */
+ function class_uses_recursive($class): array
+ {
+ if (is_object($class)) {
+ $class = get_class($class);
+ }
+
+ $results = [];
+ $classes = array_merge([$class => $class], class_parents($class));
+ foreach ($classes as $class) {
+ $results += trait_uses_recursive($class);
+ }
+
+ return array_unique($results);
+ }
+}
diff --git a/vendor/topthink/think-helper/src/helper/Arr.php b/vendor/topthink/think-helper/src/helper/Arr.php
new file mode 100644
index 000000000..ed4d6a9ef
--- /dev/null
+++ b/vendor/topthink/think-helper/src/helper/Arr.php
@@ -0,0 +1,634 @@
+
+// +----------------------------------------------------------------------
+
+namespace think\helper;
+
+use ArrayAccess;
+use InvalidArgumentException;
+use think\Collection;
+
+class Arr
+{
+
+ /**
+ * Determine whether the given value is array accessible.
+ *
+ * @param mixed $value
+ * @return bool
+ */
+ public static function accessible($value)
+ {
+ return is_array($value) || $value instanceof ArrayAccess;
+ }
+
+ /**
+ * Add an element to an array using "dot" notation if it doesn't exist.
+ *
+ * @param array $array
+ * @param string $key
+ * @param mixed $value
+ * @return array
+ */
+ public static function add($array, $key, $value)
+ {
+ if (is_null(static::get($array, $key))) {
+ static::set($array, $key, $value);
+ }
+
+ return $array;
+ }
+
+ /**
+ * Collapse an array of arrays into a single array.
+ *
+ * @param array $array
+ * @return array
+ */
+ public static function collapse($array)
+ {
+ $results = [];
+
+ foreach ($array as $values) {
+ if ($values instanceof Collection) {
+ $values = $values->all();
+ } elseif (!is_array($values)) {
+ continue;
+ }
+
+ $results = array_merge($results, $values);
+ }
+
+ return $results;
+ }
+
+ /**
+ * Cross join the given arrays, returning all possible permutations.
+ *
+ * @param array ...$arrays
+ * @return array
+ */
+ public static function crossJoin(...$arrays)
+ {
+ $results = [[]];
+
+ foreach ($arrays as $index => $array) {
+ $append = [];
+
+ foreach ($results as $product) {
+ foreach ($array as $item) {
+ $product[$index] = $item;
+
+ $append[] = $product;
+ }
+ }
+
+ $results = $append;
+ }
+
+ return $results;
+ }
+
+ /**
+ * Divide an array into two arrays. One with keys and the other with values.
+ *
+ * @param array $array
+ * @return array
+ */
+ public static function divide($array)
+ {
+ return [array_keys($array), array_values($array)];
+ }
+
+ /**
+ * Flatten a multi-dimensional associative array with dots.
+ *
+ * @param array $array
+ * @param string $prepend
+ * @return array
+ */
+ public static function dot($array, $prepend = '')
+ {
+ $results = [];
+
+ foreach ($array as $key => $value) {
+ if (is_array($value) && !empty($value)) {
+ $results = array_merge($results, static::dot($value, $prepend . $key . '.'));
+ } else {
+ $results[$prepend . $key] = $value;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Get all of the given array except for a specified array of keys.
+ *
+ * @param array $array
+ * @param array|string $keys
+ * @return array
+ */
+ public static function except($array, $keys)
+ {
+ static::forget($array, $keys);
+
+ return $array;
+ }
+
+ /**
+ * Determine if the given key exists in the provided array.
+ *
+ * @param \ArrayAccess|array $array
+ * @param string|int $key
+ * @return bool
+ */
+ public static function exists($array, $key)
+ {
+ if ($array instanceof ArrayAccess) {
+ return $array->offsetExists($key);
+ }
+
+ return array_key_exists($key, $array);
+ }
+
+ /**
+ * Return the first element in an array passing a given truth test.
+ *
+ * @param array $array
+ * @param callable|null $callback
+ * @param mixed $default
+ * @return mixed
+ */
+ public static function first($array, callable $callback = null, $default = null)
+ {
+ if (is_null($callback)) {
+ if (empty($array)) {
+ return value($default);
+ }
+
+ foreach ($array as $item) {
+ return $item;
+ }
+ }
+
+ foreach ($array as $key => $value) {
+ if (call_user_func($callback, $value, $key)) {
+ return $value;
+ }
+ }
+
+ return value($default);
+ }
+
+ /**
+ * Return the last element in an array passing a given truth test.
+ *
+ * @param array $array
+ * @param callable|null $callback
+ * @param mixed $default
+ * @return mixed
+ */
+ public static function last($array, callable $callback = null, $default = null)
+ {
+ if (is_null($callback)) {
+ return empty($array) ? value($default) : end($array);
+ }
+
+ return static::first(array_reverse($array, true), $callback, $default);
+ }
+
+ /**
+ * Flatten a multi-dimensional array into a single level.
+ *
+ * @param array $array
+ * @param int $depth
+ * @return array
+ */
+ public static function flatten($array, $depth = INF)
+ {
+ $result = [];
+
+ foreach ($array as $item) {
+ $item = $item instanceof Collection ? $item->all() : $item;
+
+ if (!is_array($item)) {
+ $result[] = $item;
+ } elseif ($depth === 1) {
+ $result = array_merge($result, array_values($item));
+ } else {
+ $result = array_merge($result, static::flatten($item, $depth - 1));
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Remove one or many array items from a given array using "dot" notation.
+ *
+ * @param array $array
+ * @param array|string $keys
+ * @return void
+ */
+ public static function forget(&$array, $keys)
+ {
+ $original = &$array;
+
+ $keys = (array) $keys;
+
+ if (count($keys) === 0) {
+ return;
+ }
+
+ foreach ($keys as $key) {
+ // if the exact key exists in the top-level, remove it
+ if (static::exists($array, $key)) {
+ unset($array[$key]);
+
+ continue;
+ }
+
+ $parts = explode('.', $key);
+
+ // clean up before each pass
+ $array = &$original;
+
+ while (count($parts) > 1) {
+ $part = array_shift($parts);
+
+ if (isset($array[$part]) && is_array($array[$part])) {
+ $array = &$array[$part];
+ } else {
+ continue 2;
+ }
+ }
+
+ unset($array[array_shift($parts)]);
+ }
+ }
+
+ /**
+ * Get an item from an array using "dot" notation.
+ *
+ * @param \ArrayAccess|array $array
+ * @param string $key
+ * @param mixed $default
+ * @return mixed
+ */
+ public static function get($array, $key, $default = null)
+ {
+ if (!static::accessible($array)) {
+ return value($default);
+ }
+
+ if (is_null($key)) {
+ return $array;
+ }
+
+ if (static::exists($array, $key)) {
+ return $array[$key];
+ }
+
+ if (strpos($key, '.') === false) {
+ return $array[$key] ?? value($default);
+ }
+
+ foreach (explode('.', $key) as $segment) {
+ if (static::accessible($array) && static::exists($array, $segment)) {
+ $array = $array[$segment];
+ } else {
+ return value($default);
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Check if an item or items exist in an array using "dot" notation.
+ *
+ * @param \ArrayAccess|array $array
+ * @param string|array $keys
+ * @return bool
+ */
+ public static function has($array, $keys)
+ {
+ $keys = (array) $keys;
+
+ if (!$array || $keys === []) {
+ return false;
+ }
+
+ foreach ($keys as $key) {
+ $subKeyArray = $array;
+
+ if (static::exists($array, $key)) {
+ continue;
+ }
+
+ foreach (explode('.', $key) as $segment) {
+ if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) {
+ $subKeyArray = $subKeyArray[$segment];
+ } else {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines if an array is associative.
+ *
+ * An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
+ *
+ * @param array $array
+ * @return bool
+ */
+ public static function isAssoc(array $array)
+ {
+ $keys = array_keys($array);
+
+ return array_keys($keys) !== $keys;
+ }
+
+ /**
+ * Get a subset of the items from the given array.
+ *
+ * @param array $array
+ * @param array|string $keys
+ * @return array
+ */
+ public static function only($array, $keys)
+ {
+ return array_intersect_key($array, array_flip((array) $keys));
+ }
+
+ /**
+ * Pluck an array of values from an array.
+ *
+ * @param array $array
+ * @param string|array $value
+ * @param string|array|null $key
+ * @return array
+ */
+ public static function pluck($array, $value, $key = null)
+ {
+ $results = [];
+
+ [$value, $key] = static::explodePluckParameters($value, $key);
+
+ foreach ($array as $item) {
+ $itemValue = data_get($item, $value);
+
+ // If the key is "null", we will just append the value to the array and keep
+ // looping. Otherwise we will key the array using the value of the key we
+ // received from the developer. Then we'll return the final array form.
+ if (is_null($key)) {
+ $results[] = $itemValue;
+ } else {
+ $itemKey = data_get($item, $key);
+
+ if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
+ $itemKey = (string) $itemKey;
+ }
+
+ $results[$itemKey] = $itemValue;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Explode the "value" and "key" arguments passed to "pluck".
+ *
+ * @param string|array $value
+ * @param string|array|null $key
+ * @return array
+ */
+ protected static function explodePluckParameters($value, $key)
+ {
+ $value = is_string($value) ? explode('.', $value) : $value;
+
+ $key = is_null($key) || is_array($key) ? $key : explode('.', $key);
+
+ return [$value, $key];
+ }
+
+ /**
+ * Push an item onto the beginning of an array.
+ *
+ * @param array $array
+ * @param mixed $value
+ * @param mixed $key
+ * @return array
+ */
+ public static function prepend($array, $value, $key = null)
+ {
+ if (is_null($key)) {
+ array_unshift($array, $value);
+ } else {
+ $array = [$key => $value] + $array;
+ }
+
+ return $array;
+ }
+
+ /**
+ * Get a value from the array, and remove it.
+ *
+ * @param array $array
+ * @param string $key
+ * @param mixed $default
+ * @return mixed
+ */
+ public static function pull(&$array, $key, $default = null)
+ {
+ $value = static::get($array, $key, $default);
+
+ static::forget($array, $key);
+
+ return $value;
+ }
+
+ /**
+ * Get one or a specified number of random values from an array.
+ *
+ * @param array $array
+ * @param int|null $number
+ * @return mixed
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function random($array, $number = null)
+ {
+ $requested = is_null($number) ? 1 : $number;
+
+ $count = count($array);
+
+ if ($requested > $count) {
+ throw new InvalidArgumentException(
+ "You requested {$requested} items, but there are only {$count} items available."
+ );
+ }
+
+ if (is_null($number)) {
+ return $array[array_rand($array)];
+ }
+
+ if ((int) $number === 0) {
+ return [];
+ }
+
+ $keys = array_rand($array, $number);
+
+ $results = [];
+
+ foreach ((array) $keys as $key) {
+ $results[] = $array[$key];
+ }
+
+ return $results;
+ }
+
+ /**
+ * Set an array item to a given value using "dot" notation.
+ *
+ * If no key is given to the method, the entire array will be replaced.
+ *
+ * @param array $array
+ * @param string $key
+ * @param mixed $value
+ * @return array
+ */
+ public static function set(&$array, $key, $value)
+ {
+ if (is_null($key)) {
+ return $array = $value;
+ }
+
+ $keys = explode('.', $key);
+
+ while (count($keys) > 1) {
+ $key = array_shift($keys);
+
+ // If the key doesn't exist at this depth, we will just create an empty array
+ // to hold the next value, allowing us to create the arrays to hold final
+ // values at the correct depth. Then we'll keep digging into the array.
+ if (!isset($array[$key]) || !is_array($array[$key])) {
+ $array[$key] = [];
+ }
+
+ $array = &$array[$key];
+ }
+
+ $array[array_shift($keys)] = $value;
+
+ return $array;
+ }
+
+ /**
+ * Shuffle the given array and return the result.
+ *
+ * @param array $array
+ * @param int|null $seed
+ * @return array
+ */
+ public static function shuffle($array, $seed = null)
+ {
+ if (is_null($seed)) {
+ shuffle($array);
+ } else {
+ srand($seed);
+
+ usort($array, function () {
+ return rand(-1, 1);
+ });
+ }
+
+ return $array;
+ }
+
+ /**
+ * Sort the array using the given callback or "dot" notation.
+ *
+ * @param array $array
+ * @param callable|string|null $callback
+ * @return array
+ */
+ public static function sort($array, $callback = null)
+ {
+ return Collection::make($array)->sort($callback)->all();
+ }
+
+ /**
+ * Recursively sort an array by keys and values.
+ *
+ * @param array $array
+ * @return array
+ */
+ public static function sortRecursive($array)
+ {
+ foreach ($array as &$value) {
+ if (is_array($value)) {
+ $value = static::sortRecursive($value);
+ }
+ }
+
+ if (static::isAssoc($array)) {
+ ksort($array);
+ } else {
+ sort($array);
+ }
+
+ return $array;
+ }
+
+ /**
+ * Convert the array into a query string.
+ *
+ * @param array $array
+ * @return string
+ */
+ public static function query($array)
+ {
+ return http_build_query($array, null, '&', PHP_QUERY_RFC3986);
+ }
+
+ /**
+ * Filter the array using the given callback.
+ *
+ * @param array $array
+ * @param callable $callback
+ * @return array
+ */
+ public static function where($array, callable $callback)
+ {
+ return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
+ }
+
+ /**
+ * If the given value is not an array and not null, wrap it in one.
+ *
+ * @param mixed $value
+ * @return array
+ */
+ public static function wrap($value)
+ {
+ if (is_null($value)) {
+ return [];
+ }
+
+ return is_array($value) ? $value : [$value];
+ }
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-helper/src/helper/Str.php b/vendor/topthink/think-helper/src/helper/Str.php
new file mode 100644
index 000000000..7391fbd39
--- /dev/null
+++ b/vendor/topthink/think-helper/src/helper/Str.php
@@ -0,0 +1,234 @@
+
+// +----------------------------------------------------------------------
+namespace think\helper;
+
+class Str
+{
+
+ protected static $snakeCache = [];
+
+ protected static $camelCache = [];
+
+ protected static $studlyCache = [];
+
+ /**
+ * 检查字符串中是否包含某些字符串
+ * @param string $haystack
+ * @param string|array $needles
+ * @return bool
+ */
+ public static function contains(string $haystack, $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if ('' != $needle && mb_strpos($haystack, $needle) !== false) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 检查字符串是否以某些字符串结尾
+ *
+ * @param string $haystack
+ * @param string|array $needles
+ * @return bool
+ */
+ public static function endsWith(string $haystack, $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if ((string) $needle === static::substr($haystack, -static::length($needle))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 检查字符串是否以某些字符串开头
+ *
+ * @param string $haystack
+ * @param string|array $needles
+ * @return bool
+ */
+ public static function startsWith(string $haystack, $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if ('' != $needle && mb_strpos($haystack, $needle) === 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 获取指定长度的随机字母数字组合的字符串
+ *
+ * @param int $length
+ * @param int $type
+ * @param string $addChars
+ * @return string
+ */
+ public static function random(int $length = 6, int $type = null, string $addChars = ''): string
+ {
+ $str = '';
+ switch ($type) {
+ case 0:
+ $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' . $addChars;
+ break;
+ case 1:
+ $chars = str_repeat('0123456789', 3);
+ break;
+ case 2:
+ $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . $addChars;
+ break;
+ case 3:
+ $chars = 'abcdefghijklmnopqrstuvwxyz' . $addChars;
+ break;
+ case 4:
+ $chars = "们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书" . $addChars;
+ break;
+ default:
+ $chars = 'ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789' . $addChars;
+ break;
+ }
+ if ($length > 10) {
+ $chars = $type == 1 ? str_repeat($chars, $length) : str_repeat($chars, 5);
+ }
+ if ($type != 4) {
+ $chars = str_shuffle($chars);
+ $str = substr($chars, 0, $length);
+ } else {
+ for ($i = 0; $i < $length; $i++) {
+ $str .= mb_substr($chars, floor(mt_rand(0, mb_strlen($chars, 'utf-8') - 1)), 1);
+ }
+ }
+ return $str;
+ }
+
+ /**
+ * 字符串转小写
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function lower(string $value): string
+ {
+ return mb_strtolower($value, 'UTF-8');
+ }
+
+ /**
+ * 字符串转大写
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function upper(string $value): string
+ {
+ return mb_strtoupper($value, 'UTF-8');
+ }
+
+ /**
+ * 获取字符串的长度
+ *
+ * @param string $value
+ * @return int
+ */
+ public static function length(string $value): int
+ {
+ return mb_strlen($value);
+ }
+
+ /**
+ * 截取字符串
+ *
+ * @param string $string
+ * @param int $start
+ * @param int|null $length
+ * @return string
+ */
+ public static function substr(string $string, int $start, int $length = null): string
+ {
+ return mb_substr($string, $start, $length, 'UTF-8');
+ }
+
+ /**
+ * 驼峰转下划线
+ *
+ * @param string $value
+ * @param string $delimiter
+ * @return string
+ */
+ public static function snake(string $value, string $delimiter = '_'): string
+ {
+ $key = $value;
+
+ if (isset(static::$snakeCache[$key][$delimiter])) {
+ return static::$snakeCache[$key][$delimiter];
+ }
+
+ if (!ctype_lower($value)) {
+ $value = preg_replace('/\s+/u', '', $value);
+
+ $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value));
+ }
+
+ return static::$snakeCache[$key][$delimiter] = $value;
+ }
+
+ /**
+ * 下划线转驼峰(首字母小写)
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function camel(string $value): string
+ {
+ if (isset(static::$camelCache[$value])) {
+ return static::$camelCache[$value];
+ }
+
+ return static::$camelCache[$value] = lcfirst(static::studly($value));
+ }
+
+ /**
+ * 下划线转驼峰(首字母大写)
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function studly(string $value): string
+ {
+ $key = $value;
+
+ if (isset(static::$studlyCache[$key])) {
+ return static::$studlyCache[$key];
+ }
+
+ $value = ucwords(str_replace(['-', '_'], ' ', $value));
+
+ return static::$studlyCache[$key] = str_replace(' ', '', $value);
+ }
+
+ /**
+ * 转为首字母大写的标题格式
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function title(string $value): string
+ {
+ return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
+ }
+}
diff --git a/vendor/topthink/think-multi-app/LICENSE b/vendor/topthink/think-multi-app/LICENSE
new file mode 100644
index 000000000..261eeb9e9
--- /dev/null
+++ b/vendor/topthink/think-multi-app/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/topthink/think-multi-app/README.md b/vendor/topthink/think-multi-app/README.md
new file mode 100644
index 000000000..a746fa7a7
--- /dev/null
+++ b/vendor/topthink/think-multi-app/README.md
@@ -0,0 +1,14 @@
+# think-multi-app
+
+用于ThinkPHP6+的多应用支持
+
+## 安装
+
+~~~
+composer require topthink/think-multi-app
+~~~
+
+## 使用
+
+用法参考ThinkPHP6完全开发手册[多应用模式](https://www.kancloud.cn/manual/thinkphp6_0/1297876)章节。
+
diff --git a/vendor/topthink/think-multi-app/composer.json b/vendor/topthink/think-multi-app/composer.json
new file mode 100644
index 000000000..92d620eb1
--- /dev/null
+++ b/vendor/topthink/think-multi-app/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "topthink/think-multi-app",
+ "description": "thinkphp6 multi app support",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "liu21st",
+ "email": "liu21st@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=7.1.0",
+ "topthink/framework": "^6.0.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "think\\app\\": "src"
+ }
+ },
+ "extra": {
+ "think":{
+ "services":[
+ "think\\app\\Service"
+ ]
+ }
+ },
+ "minimum-stability": "dev"
+}
diff --git a/vendor/topthink/think-multi-app/src/MultiApp.php b/vendor/topthink/think-multi-app/src/MultiApp.php
new file mode 100644
index 000000000..b0ac260d9
--- /dev/null
+++ b/vendor/topthink/think-multi-app/src/MultiApp.php
@@ -0,0 +1,245 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\app;
+
+use Closure;
+use think\App;
+use think\exception\HttpException;
+use think\Request;
+use think\Response;
+
+/**
+ * 多应用模式支持
+ */
+class MultiApp
+{
+
+ /** @var App */
+ protected $app;
+
+ /**
+ * 应用名称
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * 应用名称
+ * @var string
+ */
+ protected $appName;
+
+ /**
+ * 应用路径
+ * @var string
+ */
+ protected $path;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ $this->name = $this->app->http->getName();
+ $this->path = $this->app->http->getPath();
+ }
+
+ /**
+ * 多应用解析
+ * @access public
+ * @param Request $request
+ * @param Closure $next
+ * @return Response
+ */
+ public function handle($request, Closure $next)
+ {
+ if (!$this->parseMultiApp()) {
+ return $next($request);
+ }
+
+ return $this->app->middleware->pipeline('app')
+ ->send($request)
+ ->then(function ($request) use ($next) {
+ return $next($request);
+ });
+ }
+
+ /**
+ * 获取路由目录
+ * @access protected
+ * @return string
+ */
+ protected function getRoutePath(): string
+ {
+ return $this->app->getAppPath() . 'route' . DIRECTORY_SEPARATOR;
+ }
+
+ /**
+ * 解析多应用
+ * @return bool
+ */
+ protected function parseMultiApp(): bool
+ {
+ $scriptName = $this->getScriptName();
+ $defaultApp = $this->app->config->get('app.default_app') ?: 'index';
+
+ if ($this->name || ($scriptName && !in_array($scriptName, ['index', 'router', 'think']))) {
+ $appName = $this->name ?: $scriptName;
+ $this->app->http->setBind();
+ } else {
+ // 自动多应用识别
+ $this->app->http->setBind(false);
+ $appName = null;
+ $this->appName = '';
+
+ $bind = $this->app->config->get('app.domain_bind', []);
+
+ if (!empty($bind)) {
+ // 获取当前子域名
+ $subDomain = $this->app->request->subDomain();
+ $domain = $this->app->request->host(true);
+
+ if (isset($bind[$domain])) {
+ $appName = $bind[$domain];
+ $this->app->http->setBind();
+ } elseif (isset($bind[$subDomain])) {
+ $appName = $bind[$subDomain];
+ $this->app->http->setBind();
+ } elseif (isset($bind['*'])) {
+ $appName = $bind['*'];
+ $this->app->http->setBind();
+ }
+ }
+
+ if (!$this->app->http->isBind()) {
+ $path = $this->app->request->pathinfo();
+ $map = $this->app->config->get('app.app_map', []);
+ $deny = $this->app->config->get('app.deny_app_list', []);
+ $name = current(explode('/', $path));
+
+ if (strpos($name, '.')) {
+ $name = strstr($name, '.', true);
+ }
+
+ if (isset($map[$name])) {
+ if ($map[$name] instanceof Closure) {
+ $result = call_user_func_array($map[$name], [$this->app]);
+ $appName = $result ?: $name;
+ } else {
+ $appName = $map[$name];
+ }
+ } elseif ($name && (false !== array_search($name, $map) || in_array($name, $deny))) {
+ throw new HttpException(404, 'app not exists:' . $name);
+ } elseif ($name && isset($map['*'])) {
+ $appName = $map['*'];
+ } else {
+ $appName = $name ?: $defaultApp;
+ $appPath = $this->path ?: $this->app->getBasePath() . $appName . DIRECTORY_SEPARATOR;
+
+ if (!is_dir($appPath)) {
+ $express = $this->app->config->get('app.app_express', false);
+ if ($express) {
+ $this->setApp($defaultApp);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ if ($name) {
+ $this->app->request->setRoot('/' . $name);
+ $this->app->request->setPathinfo(strpos($path, '/') ? ltrim(strstr($path, '/'), '/') : '');
+ }
+ }
+ }
+
+ $this->setApp($appName ?: $defaultApp);
+ return true;
+ }
+
+ /**
+ * 获取当前运行入口名称
+ * @access protected
+ * @codeCoverageIgnore
+ * @return string
+ */
+ protected function getScriptName(): string
+ {
+ if (isset($_SERVER['SCRIPT_FILENAME'])) {
+ $file = $_SERVER['SCRIPT_FILENAME'];
+ } elseif (isset($_SERVER['argv'][0])) {
+ $file = realpath($_SERVER['argv'][0]);
+ }
+
+ return isset($file) ? pathinfo($file, PATHINFO_FILENAME) : '';
+ }
+
+ /**
+ * 设置应用
+ * @param string $appName
+ */
+ protected function setApp(string $appName): void
+ {
+ $this->appName = $appName;
+ $this->app->http->name($appName);
+
+ $appPath = $this->path ?: $this->app->getBasePath() . $appName . DIRECTORY_SEPARATOR;
+
+ $this->app->setAppPath($appPath);
+ // 设置应用命名空间
+ $this->app->setNamespace($this->app->config->get('app.app_namespace') ?: 'app\\' . $appName);
+
+ if (is_dir($appPath)) {
+ $this->app->setRuntimePath($this->app->getRuntimePath() . $appName . DIRECTORY_SEPARATOR);
+ $this->app->http->setRoutePath($this->getRoutePath());
+
+ //加载应用
+ $this->loadApp($appName, $appPath);
+ }
+ }
+
+ /**
+ * 加载应用文件
+ * @param string $appName 应用名
+ * @return void
+ */
+ protected function loadApp(string $appName, string $appPath): void
+ {
+ if (is_file($appPath . 'common.php')) {
+ include_once $appPath . 'common.php';
+ }
+
+ $files = [];
+
+ $files = array_merge($files, glob($appPath . 'config' . DIRECTORY_SEPARATOR . '*' . $this->app->getConfigExt()));
+
+ foreach ($files as $file) {
+ $this->app->config->load($file, pathinfo($file, PATHINFO_FILENAME));
+ }
+
+ if (is_file($appPath . 'event.php')) {
+ $this->app->loadEvent(include $appPath . 'event.php');
+ }
+
+ if (is_file($appPath . 'middleware.php')) {
+ $this->app->middleware->import(include $appPath . 'middleware.php', 'app');
+ }
+
+ if (is_file($appPath . 'provider.php')) {
+ $this->app->bind(include $appPath . 'provider.php');
+ }
+
+ // 加载应用默认语言包
+ $this->app->loadLangPack($this->app->lang->defaultLangSet());
+ }
+
+}
diff --git a/vendor/topthink/think-multi-app/src/Service.php b/vendor/topthink/think-multi-app/src/Service.php
new file mode 100644
index 000000000..22b85320d
--- /dev/null
+++ b/vendor/topthink/think-multi-app/src/Service.php
@@ -0,0 +1,32 @@
+
+// +----------------------------------------------------------------------
+namespace think\app;
+
+use think\Service as BaseService;
+
+class Service extends BaseService
+{
+ public function boot()
+ {
+ $this->app->event->listen('HttpRun', function () {
+ $this->app->middleware->add(MultiApp::class);
+ });
+
+ $this->commands([
+ 'build' => command\Build::class,
+ 'clear' => command\Clear::class,
+ ]);
+
+ $this->app->bind([
+ 'think\route\Url' => Url::class,
+ ]);
+ }
+}
diff --git a/vendor/topthink/think-multi-app/src/Url.php b/vendor/topthink/think-multi-app/src/Url.php
new file mode 100644
index 000000000..7bd6057f9
--- /dev/null
+++ b/vendor/topthink/think-multi-app/src/Url.php
@@ -0,0 +1,232 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\app;
+
+use think\App;
+use think\Route;
+use think\route\Url as UrlBuild;
+
+/**
+ * 路由地址生成
+ */
+class Url extends UrlBuild
+{
+ /**
+ * 直接解析URL地址
+ * @access protected
+ * @param string $url URL
+ * @param string|bool $domain Domain
+ * @return string
+ */
+ protected function parseUrl(string $url, &$domain): string
+ {
+ $request = $this->app->request;
+
+ if (0 === strpos($url, '/')) {
+ // 直接作为路由地址解析
+ $url = substr($url, 1);
+ } elseif (false !== strpos($url, '\\')) {
+ // 解析到类
+ $url = ltrim(str_replace('\\', '/', $url), '/');
+ } elseif (0 === strpos($url, '@')) {
+ // 解析到控制器
+ $url = substr($url, 1);
+ } elseif ('' === $url) {
+ $url = $this->getAppName() . '/' . $request->controller() . '/' . $request->action();
+ } else {
+ // 解析到 应用/控制器/操作
+ $controller = $request->controller();
+ $app = $this->getAppName();
+ $path = explode('/', $url);
+ $action = array_pop($path);
+ $controller = empty($path) ? $controller : array_pop($path);
+ $app = empty($path) ? $app : array_pop($path);
+ $url = $controller . '/' . $action;
+ $bind = $this->app->config->get('app.domain_bind', []);
+
+ if ($key = array_search($this->app->http->getName(), $bind)) {
+ isset($bind[$_SERVER['SERVER_NAME']]) && $domain = $_SERVER['SERVER_NAME'];
+
+ $domain = is_bool($domain) ? $key : $domain;
+ } else {
+ $url = $app . '/' . $url;
+ }
+ }
+
+ return $url;
+ }
+
+ public function build()
+ {
+ // 解析URL
+ $url = $this->url;
+ $suffix = $this->suffix;
+ $domain = $this->domain;
+ $request = $this->app->request;
+ $vars = $this->vars;
+
+ if (0 === strpos($url, '[') && $pos = strpos($url, ']')) {
+ // [name] 表示使用路由命名标识生成URL
+ $name = substr($url, 1, $pos - 1);
+ $url = 'name' . substr($url, $pos + 1);
+ }
+
+ if (false === strpos($url, '://') && 0 !== strpos($url, '/')) {
+ $info = parse_url($url);
+ $url = !empty($info['path']) ? $info['path'] : '';
+
+ if (isset($info['fragment'])) {
+ // 解析锚点
+ $anchor = $info['fragment'];
+
+ if (false !== strpos($anchor, '?')) {
+ // 解析参数
+ list($anchor, $info['query']) = explode('?', $anchor, 2);
+ }
+
+ if (false !== strpos($anchor, '@')) {
+ // 解析域名
+ list($anchor, $domain) = explode('@', $anchor, 2);
+ }
+ } elseif (strpos($url, '@') && false === strpos($url, '\\')) {
+ // 解析域名
+ list($url, $domain) = explode('@', $url, 2);
+ }
+ }
+
+ if ($url) {
+ $checkName = isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '');
+ $checkDomain = $domain && is_string($domain) ? $domain : null;
+
+ $rule = $this->route->getName($checkName, $checkDomain);
+
+ if (empty($rule) && isset($info['query'])) {
+ $rule = $this->route->getName($url, $checkDomain);
+ // 解析地址里面参数 合并到vars
+ parse_str($info['query'], $params);
+ $vars = array_merge($params, $vars);
+ unset($info['query']);
+ }
+ }
+
+ if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars, $domain)) {
+ // 匹配路由命名标识
+ $url = $match[0];
+
+ if ($domain && !empty($match[1])) {
+ $domain = $match[1];
+ }
+
+ if (!is_null($match[2])) {
+ $suffix = $match[2];
+ }
+
+ if (!$this->app->http->isBind()) {
+ $app = $this->getAppName();
+ $url = $app . '/' . $url;
+ }
+ } elseif (!empty($rule) && isset($name)) {
+ throw new \InvalidArgumentException('route name not exists:' . $name);
+ } else {
+ // 检测URL绑定
+ $bind = $this->route->getDomainBind($domain && is_string($domain) ? $domain : null);
+
+ if ($bind && 0 === strpos($url, $bind)) {
+ $url = substr($url, strlen($bind) + 1);
+ } else {
+ $binds = $this->route->getBind();
+
+ foreach ($binds as $key => $val) {
+ if (is_string($val) && 0 === strpos($url, $val) && substr_count($val, '/') > 1) {
+ $url = substr($url, strlen($val) + 1);
+ $domain = $key;
+ break;
+ }
+ }
+ }
+
+ // 路由标识不存在 直接解析
+ $url = $this->parseUrl($url, $domain);
+
+ if (isset($info['query'])) {
+ // 解析地址里面参数 合并到vars
+ parse_str($info['query'], $params);
+ $vars = array_merge($params, $vars);
+ }
+ }
+
+ // 还原URL分隔符
+ $depr = $this->route->config('pathinfo_depr');
+ $url = str_replace('/', $depr, $url);
+
+ $file = $request->baseFile();
+ if ($file && 0 !== strpos($request->url(), $file)) {
+ $file = str_replace('\\', '/', dirname($file));
+ }
+
+ $url = rtrim($file, '/') . '/' . ltrim($url, '/');
+
+ // URL后缀
+ if ('/' == substr($url, -1) || '' == $url) {
+ $suffix = '';
+ } else {
+ $suffix = $this->parseSuffix($suffix);
+ }
+
+ // 锚点
+ $anchor = !empty($anchor) ? '#' . $anchor : '';
+
+ // 参数组装
+ if (!empty($vars)) {
+ // 添加参数
+ if ($this->route->config('url_common_param')) {
+ $vars = http_build_query($vars);
+ $url .= $suffix . '?' . $vars . $anchor;
+ } else {
+ foreach ($vars as $var => $val) {
+ $val = (string) $val;
+ if ('' !== $val) {
+ $url .= $depr . $var . $depr . urlencode($val);
+ }
+ }
+
+ $url .= $suffix . $anchor;
+ }
+ } else {
+ $url .= $suffix . $anchor;
+ }
+
+ // 检测域名
+ $domain = $this->parseDomain($url, $domain);
+
+ // URL组装
+ return $domain . rtrim($this->root, '/') . '/' . ltrim($url, '/');
+ }
+
+ /**
+ * 获取URL的应用名
+ * @access protected
+ * @return string
+ */
+ protected function getAppName()
+ {
+ $app = $this->app->http->getName();
+ $map = $this->app->config->get('app.app_map', []);
+
+ if ($key = array_search($app, $map)) {
+ $app = $key;
+ }
+
+ return $app;
+ }
+}
diff --git a/vendor/topthink/think-multi-app/src/command/Build.php b/vendor/topthink/think-multi-app/src/command/Build.php
new file mode 100644
index 000000000..65b2f8747
--- /dev/null
+++ b/vendor/topthink/think-multi-app/src/command/Build.php
@@ -0,0 +1,180 @@
+
+// +----------------------------------------------------------------------
+
+namespace think\app\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\Output;
+
+class Build extends Command
+{
+ /**
+ * 应用基础目录
+ * @var string
+ */
+ protected $basePath;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this->setName('build')
+ ->addArgument('app', Argument::OPTIONAL, 'app name .')
+ ->setDescription('Build App Dirs');
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $this->basePath = $this->app->getBasePath();
+ $app = $input->getArgument('app') ?: '';
+
+ if (is_file($this->basePath . 'build.php')) {
+ $list = include $this->basePath . 'build.php';
+ } else {
+ $list = [
+ '__dir__' => ['controller', 'model', 'view'],
+ ];
+ }
+
+ $this->buildApp($app, $list);
+ $output->writeln("Successed ");
+
+ }
+
+ /**
+ * 创建应用
+ * @access protected
+ * @param string $app 应用名
+ * @param array $list 目录结构
+ * @return void
+ */
+ protected function buildApp(string $app, array $list = []): void
+ {
+ if (!is_dir($this->basePath . $app)) {
+ // 创建应用目录
+ mkdir($this->basePath . $app);
+ }
+
+ $appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '');
+ $namespace = 'app' . ($app ? '\\' . $app : '');
+
+ // 创建配置文件和公共文件
+ $this->buildCommon($app);
+ // 创建模块的默认页面
+ $this->buildHello($app, $namespace);
+
+ foreach ($list as $path => $file) {
+ if ('__dir__' == $path) {
+ // 生成子目录
+ foreach ($file as $dir) {
+ $this->checkDirBuild($appPath . $dir);
+ }
+ } elseif ('__file__' == $path) {
+ // 生成(空白)文件
+ foreach ($file as $name) {
+ if (!is_file($appPath . $name)) {
+ file_put_contents($appPath . $name, 'php' == pathinfo($name, PATHINFO_EXTENSION) ? 'app->config->get('route.controller_suffix')) {
+ $filename = $appPath . $path . DIRECTORY_SEPARATOR . $val . 'Controller.php';
+ $class = $val . 'Controller';
+ }
+ $content = "checkDirBuild(dirname($filename));
+ $content = '';
+ break;
+ default:
+ // 其他文件
+ $content = "app->config->get('route.controller_suffix') ? 'Controller' : '';
+ $filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'controller' . DIRECTORY_SEPARATOR . 'Index' . $suffix . '.php';
+
+ if (!is_file($filename)) {
+ $content = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'controller.stub');
+ $content = str_replace(['{%name%}', '{%app%}', '{%layer%}', '{%suffix%}'], [$app, $namespace, 'controller', $suffix], $content);
+ $this->checkDirBuild(dirname($filename));
+
+ file_put_contents($filename, $content);
+ }
+ }
+
+ /**
+ * 创建应用的公共文件
+ * @access protected
+ * @param string $app 目录
+ * @return void
+ */
+ protected function buildCommon(string $app): void
+ {
+ $appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '');
+
+ if (!is_file($appPath . 'common.php')) {
+ file_put_contents($appPath . 'common.php', "
// +----------------------------------------------------------------------
-namespace think\console\command;
+namespace think\app\command;
use think\console\Command;
use think\console\Input;
+use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
-use think\facade\App;
-use think\facade\Cache;
class Clear extends Command
{
protected function configure()
{
// 指令配置
- $this
- ->setName('clear')
- ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null)
+ $this->setName('clear')
+ ->addArgument('app', Argument::OPTIONAL, 'app name .')
->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file')
- ->addOption('route', 'u', Option::VALUE_NONE, 'clear route cache')
->addOption('log', 'l', Option::VALUE_NONE, 'clear log file')
->addOption('dir', 'r', Option::VALUE_NONE, 'clear empty dir')
->setDescription('Clear runtime file');
@@ -34,25 +31,24 @@ class Clear extends Command
protected function execute(Input $input, Output $output)
{
- if ($input->getOption('route')) {
- Cache::clear('route_cache');
- } else {
- if ($input->getOption('cache')) {
- $path = App::getRuntimePath() . 'cache';
- } elseif ($input->getOption('log')) {
- $path = App::getRuntimePath() . 'log';
- } else {
- $path = $input->getOption('path') ?: App::getRuntimePath();
- }
+ $app = $input->getArgument('app') ?: '';
+ $runtimePath = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($app ? $app . DIRECTORY_SEPARATOR : '');
- $rmdir = $input->getOption('dir') ? true : false;
- $this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir);
+ if ($input->getOption('cache')) {
+ $path = $runtimePath . 'cache';
+ } elseif ($input->getOption('log')) {
+ $path = $runtimePath . 'log';
+ } else {
+ $path = $runtimePath;
}
+ $rmdir = $input->getOption('dir') ? true : false;
+ $this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir);
+
$output->writeln("Clear Successed ");
}
- protected function clear($path, $rmdir)
+ protected function clear(string $path, bool $rmdir): void
{
$files = is_dir($path) ? scandir($path) : [];
diff --git a/vendor/topthink/think-multi-app/src/command/stubs/controller.stub b/vendor/topthink/think-multi-app/src/command/stubs/controller.stub
new file mode 100644
index 000000000..263c4c642
--- /dev/null
+++ b/vendor/topthink/think-multi-app/src/command/stubs/controller.stub
@@ -0,0 +1,12 @@
+=7.1.0",
+ "ext-json": "*",
+ "ext-pdo": "*",
+ "psr/simple-cache": "^1.0",
+ "psr/log": "~1.0",
+ "topthink/think-helper":"^3.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7|^8|^9.5"
+ },
+ "autoload": {
+ "psr-4": {
+ "think\\": "src"
+ },
+ "files": [
+ "stubs/load_stubs.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "tests\\": "tests"
+ }
+ },
+ "config": {
+ "sort-packages": true
+ }
+}
diff --git a/vendor/topthink/think-orm/src/DbManager.php b/vendor/topthink/think-orm/src/DbManager.php
new file mode 100644
index 000000000..147d9f63e
--- /dev/null
+++ b/vendor/topthink/think-orm/src/DbManager.php
@@ -0,0 +1,376 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use InvalidArgumentException;
+use Psr\Log\LoggerInterface;
+use Psr\SimpleCache\CacheInterface;
+use think\db\BaseQuery;
+use think\db\ConnectionInterface;
+use think\db\Query;
+use think\db\Raw;
+
+/**
+ * Class DbManager
+ * @package think
+ * @mixin BaseQuery
+ * @mixin Query
+ */
+class DbManager
+{
+ /**
+ * 数据库连接实例
+ * @var array
+ */
+ protected $instance = [];
+
+ /**
+ * 数据库配置
+ * @var array
+ */
+ protected $config = [];
+
+ /**
+ * Event对象或者数组
+ * @var array|object
+ */
+ protected $event;
+
+ /**
+ * SQL监听
+ * @var array
+ */
+ protected $listen = [];
+
+ /**
+ * SQL日志
+ * @var array
+ */
+ protected $dbLog = [];
+
+ /**
+ * 查询次数
+ * @var int
+ */
+ protected $queryTimes = 0;
+
+ /**
+ * 查询缓存对象
+ * @var CacheInterface
+ */
+ protected $cache;
+
+ /**
+ * 查询日志对象
+ * @var LoggerInterface
+ */
+ protected $log;
+
+ /**
+ * 架构函数
+ * @access public
+ */
+ public function __construct()
+ {
+ $this->modelMaker();
+ }
+
+ /**
+ * 注入模型对象
+ * @access public
+ * @return void
+ */
+ protected function modelMaker()
+ {
+ Model::setDb($this);
+
+ if (is_object($this->event)) {
+ Model::setEvent($this->event);
+ }
+
+ Model::maker(function (Model $model) {
+ $isAutoWriteTimestamp = $model->getAutoWriteTimestamp();
+
+ if (is_null($isAutoWriteTimestamp)) {
+ // 自动写入时间戳
+ $model->isAutoWriteTimestamp($this->getConfig('auto_timestamp', true));
+ }
+
+ $dateFormat = $model->getDateFormat();
+
+ if (is_null($dateFormat)) {
+ // 设置时间戳格式
+ $model->setDateFormat($this->getConfig('datetime_format', 'Y-m-d H:i:s'));
+ }
+ });
+ }
+
+ /**
+ * 监听SQL
+ * @access protected
+ * @return void
+ */
+ public function triggerSql(): void
+ {}
+
+ /**
+ * 初始化配置参数
+ * @access public
+ * @param array $config 连接配置
+ * @return void
+ */
+ public function setConfig($config): void
+ {
+ $this->config = $config;
+ }
+
+ /**
+ * 设置缓存对象
+ * @access public
+ * @param CacheInterface $cache 缓存对象
+ * @return void
+ */
+ public function setCache(CacheInterface $cache): void
+ {
+ $this->cache = $cache;
+ }
+
+ /**
+ * 设置日志对象
+ * @access public
+ * @param LoggerInterface $log 日志对象
+ * @return void
+ */
+ public function setLog(LoggerInterface $log): void
+ {
+ $this->log = $log;
+ }
+
+ /**
+ * 记录SQL日志
+ * @access protected
+ * @param string $log SQL日志信息
+ * @param string $type 日志类型
+ * @return void
+ */
+ public function log(string $log, string $type = 'sql')
+ {
+ if ($this->log) {
+ $this->log->log($type, $log);
+ } else {
+ $this->dbLog[$type][] = $log;
+ }
+ }
+
+ /**
+ * 获得查询日志(没有设置日志对象使用)
+ * @access public
+ * @param bool $clear 是否清空
+ * @return array
+ */
+ public function getDbLog(bool $clear = false): array
+ {
+ $logs = $this->dbLog;
+ if ($clear) {
+ $this->dbLog = [];
+ }
+
+ return $logs;
+ }
+
+ /**
+ * 获取配置参数
+ * @access public
+ * @param string $name 配置参数
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function getConfig(string $name = '', $default = null)
+ {
+ if ('' === $name) {
+ return $this->config;
+ }
+
+ return $this->config[$name] ?? $default;
+ }
+
+ /**
+ * 创建/切换数据库连接查询
+ * @access public
+ * @param string|null $name 连接配置标识
+ * @param bool $force 强制重新连接
+ * @return ConnectionInterface
+ */
+ public function connect(string $name = null, bool $force = false)
+ {
+ return $this->instance($name, $force);
+ }
+
+ /**
+ * 创建数据库连接实例
+ * @access protected
+ * @param string|null $name 连接标识
+ * @param bool $force 强制重新连接
+ * @return ConnectionInterface
+ */
+ protected function instance(string $name = null, bool $force = false): ConnectionInterface
+ {
+ if (empty($name)) {
+ $name = $this->getConfig('default', 'mysql');
+ }
+
+ if ($force || !isset($this->instance[$name])) {
+ $this->instance[$name] = $this->createConnection($name);
+ }
+
+ return $this->instance[$name];
+ }
+
+ /**
+ * 获取连接配置
+ * @param string $name
+ * @return array
+ */
+ protected function getConnectionConfig(string $name): array
+ {
+ $connections = $this->getConfig('connections');
+ if (!isset($connections[$name])) {
+ throw new InvalidArgumentException('Undefined db config:' . $name);
+ }
+
+ return $connections[$name];
+ }
+
+ /**
+ * 创建连接
+ * @param $name
+ * @return ConnectionInterface
+ */
+ protected function createConnection(string $name): ConnectionInterface
+ {
+ $config = $this->getConnectionConfig($name);
+
+ $type = !empty($config['type']) ? $config['type'] : 'mysql';
+
+ if (false !== strpos($type, '\\')) {
+ $class = $type;
+ } else {
+ $class = '\\think\\db\\connector\\' . ucfirst($type);
+ }
+
+ /** @var ConnectionInterface $connection */
+ $connection = new $class($config);
+ $connection->setDb($this);
+
+ if ($this->cache) {
+ $connection->setCache($this->cache);
+ }
+
+ return $connection;
+ }
+
+ /**
+ * 使用表达式设置数据
+ * @access public
+ * @param string $value 表达式
+ * @return Raw
+ */
+ public function raw(string $value): Raw
+ {
+ return new Raw($value);
+ }
+
+ /**
+ * 更新查询次数
+ * @access public
+ * @return void
+ */
+ public function updateQueryTimes(): void
+ {
+ $this->queryTimes++;
+ }
+
+ /**
+ * 重置查询次数
+ * @access public
+ * @return void
+ */
+ public function clearQueryTimes(): void
+ {
+ $this->queryTimes = 0;
+ }
+
+ /**
+ * 获得查询次数
+ * @access public
+ * @return integer
+ */
+ public function getQueryTimes(): int
+ {
+ return $this->queryTimes;
+ }
+
+ /**
+ * 监听SQL执行
+ * @access public
+ * @param callable $callback 回调方法
+ * @return void
+ */
+ public function listen(callable $callback): void
+ {
+ $this->listen[] = $callback;
+ }
+
+ /**
+ * 获取监听SQL执行
+ * @access public
+ * @return array
+ */
+ public function getListen(): array
+ {
+ return $this->listen;
+ }
+
+ /**
+ * 注册回调方法
+ * @access public
+ * @param string $event 事件名
+ * @param callable $callback 回调方法
+ * @return void
+ */
+ public function event(string $event, callable $callback): void
+ {
+ $this->event[$event][] = $callback;
+ }
+
+ /**
+ * 触发事件
+ * @access public
+ * @param string $event 事件名
+ * @param mixed $params 传入参数
+ * @return mixed
+ */
+ public function trigger(string $event, $params = null)
+ {
+ if (isset($this->event[$event])) {
+ foreach ($this->event[$event] as $callback) {
+ call_user_func_array($callback, [$this]);
+ }
+ }
+ }
+
+ public function __call($method, $args)
+ {
+ return call_user_func_array([$this->connect(), $method], $args);
+ }
+}
diff --git a/vendor/topthink/think-orm/src/Model.php b/vendor/topthink/think-orm/src/Model.php
new file mode 100644
index 000000000..35c9b256d
--- /dev/null
+++ b/vendor/topthink/think-orm/src/Model.php
@@ -0,0 +1,1068 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use ArrayAccess;
+use Closure;
+use JsonSerializable;
+use think\contract\Arrayable;
+use think\contract\Jsonable;
+use think\db\BaseQuery as Query;
+
+/**
+ * Class Model
+ * @package think
+ * @mixin Query
+ * @method void onAfterRead(Model $model) static after_read事件定义
+ * @method mixed onBeforeInsert(Model $model) static before_insert事件定义
+ * @method void onAfterInsert(Model $model) static after_insert事件定义
+ * @method mixed onBeforeUpdate(Model $model) static before_update事件定义
+ * @method void onAfterUpdate(Model $model) static after_update事件定义
+ * @method mixed onBeforeWrite(Model $model) static before_write事件定义
+ * @method void onAfterWrite(Model $model) static after_write事件定义
+ * @method mixed onBeforeDelete(Model $model) static before_write事件定义
+ * @method void onAfterDelete(Model $model) static after_delete事件定义
+ * @method void onBeforeRestore(Model $model) static before_restore事件定义
+ * @method void onAfterRestore(Model $model) static after_restore事件定义
+ */
+abstract class Model implements JsonSerializable, ArrayAccess, Arrayable, Jsonable
+{
+ use model\concern\Attribute;
+ use model\concern\RelationShip;
+ use model\concern\ModelEvent;
+ use model\concern\TimeStamp;
+ use model\concern\Conversion;
+
+ /**
+ * 数据是否存在
+ * @var bool
+ */
+ private $exists = false;
+
+ /**
+ * 是否强制更新所有数据
+ * @var bool
+ */
+ private $force = false;
+
+ /**
+ * 是否Replace
+ * @var bool
+ */
+ private $replace = false;
+
+ /**
+ * 数据表后缀
+ * @var string
+ */
+ protected $suffix;
+
+ /**
+ * 更新条件
+ * @var array
+ */
+ private $updateWhere;
+
+ /**
+ * 数据库配置
+ * @var string
+ */
+ protected $connection;
+
+ /**
+ * 模型名称
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * 主键值
+ * @var string
+ */
+ protected $key;
+
+ /**
+ * 数据表名称
+ * @var string
+ */
+ protected $table;
+
+ /**
+ * 初始化过的模型.
+ * @var array
+ */
+ protected static $initialized = [];
+
+ /**
+ * 软删除字段默认值
+ * @var mixed
+ */
+ protected $defaultSoftDelete;
+
+ /**
+ * 全局查询范围
+ * @var array
+ */
+ protected $globalScope = [];
+
+ /**
+ * 延迟保存信息
+ * @var bool
+ */
+ private $lazySave = false;
+
+ /**
+ * Db对象
+ * @var DbManager
+ */
+ protected static $db;
+
+ /**
+ * 容器对象的依赖注入方法
+ * @var callable
+ */
+ protected static $invoker;
+
+ /**
+ * 服务注入
+ * @var Closure[]
+ */
+ protected static $maker = [];
+
+ /**
+ * 方法注入
+ * @var Closure[][]
+ */
+ protected static $macro = [];
+
+ /**
+ * 设置服务注入
+ * @access public
+ * @param Closure $maker
+ * @return void
+ */
+ public static function maker(Closure $maker)
+ {
+ static::$maker[] = $maker;
+ }
+
+ /**
+ * 设置方法注入
+ * @access public
+ * @param string $method
+ * @param Closure $closure
+ * @return void
+ */
+ public static function macro(string $method, Closure $closure)
+ {
+ if (!isset(static::$macro[static::class])) {
+ static::$macro[static::class] = [];
+ }
+ static::$macro[static::class][$method] = $closure;
+ }
+
+ /**
+ * 设置Db对象
+ * @access public
+ * @param DbManager $db Db对象
+ * @return void
+ */
+ public static function setDb(DbManager $db)
+ {
+ self::$db = $db;
+ }
+
+ /**
+ * 设置容器对象的依赖注入方法
+ * @access public
+ * @param callable $callable 依赖注入方法
+ * @return void
+ */
+ public static function setInvoker(callable $callable): void
+ {
+ self::$invoker = $callable;
+ }
+
+ /**
+ * 调用反射执行模型方法 支持参数绑定
+ * @access public
+ * @param mixed $method
+ * @param array $vars 参数
+ * @return mixed
+ */
+ public function invoke($method, array $vars = [])
+ {
+ if (self::$invoker) {
+ $call = self::$invoker;
+ return $call($method instanceof Closure ? $method : Closure::fromCallable([$this, $method]), $vars);
+ }
+
+ return call_user_func_array($method instanceof Closure ? $method : [$this, $method], $vars);
+ }
+
+ /**
+ * 架构函数
+ * @access public
+ * @param array $data 数据
+ */
+ public function __construct(array $data = [])
+ {
+ $this->data = $data;
+
+ if (!empty($this->data)) {
+ // 废弃字段
+ foreach ((array) $this->disuse as $key) {
+ if (array_key_exists($key, $this->data)) {
+ unset($this->data[$key]);
+ }
+ }
+ }
+
+ // 记录原始数据
+ $this->origin = $this->data;
+
+ if (empty($this->name)) {
+ // 当前模型名
+ $name = str_replace('\\', '/', static::class);
+ $this->name = basename($name);
+ }
+
+ if (!empty(static::$maker)) {
+ foreach (static::$maker as $maker) {
+ call_user_func($maker, $this);
+ }
+ }
+
+ // 执行初始化操作
+ $this->initialize();
+ }
+
+ /**
+ * 获取当前模型名称
+ * @access public
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * 创建新的模型实例
+ * @access public
+ * @param array $data 数据
+ * @param mixed $where 更新条件
+ * @return Model
+ */
+ public function newInstance(array $data = [], $where = null): Model
+ {
+ $model = new static($data);
+
+ if ($this->connection) {
+ $model->setConnection($this->connection);
+ }
+
+ if ($this->suffix) {
+ $model->setSuffix($this->suffix);
+ }
+
+ if (empty($data)) {
+ return $model;
+ }
+
+ $model->exists(true);
+
+ $model->setUpdateWhere($where);
+
+ $model->trigger('AfterRead');
+
+ return $model;
+ }
+
+ /**
+ * 设置模型的更新条件
+ * @access protected
+ * @param mixed $where 更新条件
+ * @return void
+ */
+ protected function setUpdateWhere($where): void
+ {
+ $this->updateWhere = $where;
+ }
+
+ /**
+ * 设置当前模型的数据库连接
+ * @access public
+ * @param string $connection 数据表连接标识
+ * @return $this
+ */
+ public function setConnection(string $connection)
+ {
+ $this->connection = $connection;
+ return $this;
+ }
+
+ /**
+ * 获取当前模型的数据库连接标识
+ * @access public
+ * @return string
+ */
+ public function getConnection(): string
+ {
+ return $this->connection ?: '';
+ }
+
+ /**
+ * 设置当前模型数据表的后缀
+ * @access public
+ * @param string $suffix 数据表后缀
+ * @return $this
+ */
+ public function setSuffix(string $suffix)
+ {
+ $this->suffix = $suffix;
+ return $this;
+ }
+
+ /**
+ * 获取当前模型的数据表后缀
+ * @access public
+ * @return string
+ */
+ public function getSuffix(): string
+ {
+ return $this->suffix ?: '';
+ }
+
+ /**
+ * 获取当前模型的数据库查询对象
+ * @access public
+ * @param array $scope 设置不使用的全局查询范围
+ * @return Query
+ */
+ public function db($scope = []): Query
+ {
+ /** @var Query $query */
+ $query = self::$db->connect($this->connection)
+ ->name($this->name . $this->suffix)
+ ->pk($this->pk);
+
+ if (!empty($this->table)) {
+ $query->table($this->table . $this->suffix);
+ }
+
+ $query->model($this)
+ ->json($this->json, $this->jsonAssoc)
+ ->setFieldType(array_merge($this->schema, $this->jsonType));
+
+ // 软删除
+ if (property_exists($this, 'withTrashed') && !$this->withTrashed) {
+ $this->withNoTrashed($query);
+ }
+
+ // 全局作用域
+ if (is_array($scope)) {
+ $globalScope = array_diff($this->globalScope, $scope);
+ $query->scope($globalScope);
+ }
+
+ // 返回当前模型的数据库查询对象
+ return $query;
+ }
+
+ /**
+ * 初始化模型
+ * @access private
+ * @return void
+ */
+ private function initialize(): void
+ {
+ if (!isset(static::$initialized[static::class])) {
+ static::$initialized[static::class] = true;
+ static::init();
+ }
+ }
+
+ /**
+ * 初始化处理
+ * @access protected
+ * @return void
+ */
+ protected static function init()
+ {
+ }
+
+ protected function checkData(): void
+ {
+ }
+
+ protected function checkResult($result): void
+ {
+ }
+
+ /**
+ * 更新是否强制写入数据 而不做比较(亦可用于软删除的强制删除)
+ * @access public
+ * @param bool $force
+ * @return $this
+ */
+ public function force(bool $force = true)
+ {
+ $this->force = $force;
+ return $this;
+ }
+
+ /**
+ * 判断force
+ * @access public
+ * @return bool
+ */
+ public function isForce(): bool
+ {
+ return $this->force;
+ }
+
+ /**
+ * 新增数据是否使用Replace
+ * @access public
+ * @param bool $replace
+ * @return $this
+ */
+ public function replace(bool $replace = true)
+ {
+ $this->replace = $replace;
+ return $this;
+ }
+
+ /**
+ * 刷新模型数据
+ * @access public
+ * @param bool $relation 是否刷新关联数据
+ * @return $this
+ */
+ public function refresh(bool $relation = false)
+ {
+ if ($this->exists) {
+ $this->data = $this->db()->find($this->getKey())->getData();
+ $this->origin = $this->data;
+ $this->get = [];
+
+ if ($relation) {
+ $this->relation = [];
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置数据是否存在
+ * @access public
+ * @param bool $exists
+ * @return $this
+ */
+ public function exists(bool $exists = true)
+ {
+ $this->exists = $exists;
+ return $this;
+ }
+
+ /**
+ * 判断数据是否存在数据库
+ * @access public
+ * @return bool
+ */
+ public function isExists(): bool
+ {
+ return $this->exists;
+ }
+
+ /**
+ * 判断模型是否为空
+ * @access public
+ * @return bool
+ */
+ public function isEmpty(): bool
+ {
+ return empty($this->data);
+ }
+
+ /**
+ * 延迟保存当前数据对象
+ * @access public
+ * @param array|bool $data 数据
+ * @return void
+ */
+ public function lazySave($data = []): void
+ {
+ if (false === $data) {
+ $this->lazySave = false;
+ } else {
+ if (is_array($data)) {
+ $this->setAttrs($data);
+ }
+
+ $this->lazySave = true;
+ }
+ }
+
+ /**
+ * 保存当前数据对象
+ * @access public
+ * @param array $data 数据
+ * @param string $sequence 自增序列名
+ * @return bool
+ */
+ public function save(array $data = [], string $sequence = null): bool
+ {
+ // 数据对象赋值
+ $this->setAttrs($data);
+
+ if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) {
+ return false;
+ }
+
+ $result = $this->exists ? $this->updateData() : $this->insertData($sequence);
+
+ if (false === $result) {
+ return false;
+ }
+
+ // 写入回调
+ $this->trigger('AfterWrite');
+
+ // 重新记录原始数据
+ $this->origin = $this->data;
+ $this->get = [];
+ $this->lazySave = false;
+
+ return true;
+ }
+
+ /**
+ * 检查数据是否允许写入
+ * @access protected
+ * @return array
+ */
+ protected function checkAllowFields(): array
+ {
+ // 检测字段
+ if (empty($this->field)) {
+ if (!empty($this->schema)) {
+ $this->field = array_keys(array_merge($this->schema, $this->jsonType));
+ } else {
+ $query = $this->db();
+ $table = $this->table ? $this->table . $this->suffix : $query->getTable();
+
+ $this->field = $query->getConnection()->getTableFields($table);
+ }
+
+ return $this->field;
+ }
+
+ $field = $this->field;
+
+ if ($this->autoWriteTimestamp) {
+ array_push($field, $this->createTime, $this->updateTime);
+ }
+
+ if (!empty($this->disuse)) {
+ // 废弃字段
+ $field = array_diff($field, $this->disuse);
+ }
+
+ return $field;
+ }
+
+ /**
+ * 保存写入数据
+ * @access protected
+ * @return bool
+ */
+ protected function updateData(): bool
+ {
+ // 事件回调
+ if (false === $this->trigger('BeforeUpdate')) {
+ return false;
+ }
+
+ $this->checkData();
+
+ // 获取有更新的数据
+ $data = $this->getChangedData();
+
+ if (empty($data)) {
+ // 关联更新
+ if (!empty($this->relationWrite)) {
+ $this->autoRelationUpdate();
+ }
+
+ return true;
+ }
+
+ if ($this->autoWriteTimestamp && $this->updateTime) {
+ // 自动写入更新时间
+ $data[$this->updateTime] = $this->autoWriteTimestamp();
+ $this->data[$this->updateTime] = $this->getTimestampValue($data[$this->updateTime]);
+ }
+
+ // 检查允许字段
+ $allowFields = $this->checkAllowFields();
+
+ foreach ($this->relationWrite as $name => $val) {
+ if (!is_array($val)) {
+ continue;
+ }
+
+ foreach ($val as $key) {
+ if (isset($data[$key])) {
+ unset($data[$key]);
+ }
+ }
+ }
+
+ // 模型更新
+ $db = $this->db();
+
+ $db->transaction(function () use ($data, $allowFields, $db) {
+ $this->key = null;
+ $where = $this->getWhere();
+
+ $result = $db->where($where)
+ ->strict(false)
+ ->cache(true)
+ ->setOption('key', $this->key)
+ ->field($allowFields)
+ ->update($data);
+
+ $this->checkResult($result);
+
+ // 关联更新
+ if (!empty($this->relationWrite)) {
+ $this->autoRelationUpdate();
+ }
+ });
+
+ // 更新回调
+ $this->trigger('AfterUpdate');
+
+ return true;
+ }
+
+ /**
+ * 新增写入数据
+ * @access protected
+ * @param string $sequence 自增名
+ * @return bool
+ */
+ protected function insertData(string $sequence = null): bool
+ {
+ if (false === $this->trigger('BeforeInsert')) {
+ return false;
+ }
+
+ $this->checkData();
+ $data = $this->data;
+
+ // 时间戳自动写入
+ if ($this->autoWriteTimestamp) {
+ if ($this->createTime && !isset($data[$this->createTime])) {
+ $data[$this->createTime] = $this->autoWriteTimestamp();
+ $this->data[$this->createTime] = $this->getTimestampValue($data[$this->createTime]);
+ }
+
+ if ($this->updateTime && !isset($data[$this->updateTime])) {
+ $data[$this->updateTime] = $this->autoWriteTimestamp();
+ $this->data[$this->updateTime] = $this->getTimestampValue($data[$this->updateTime]);
+ }
+ }
+
+ // 检查允许字段
+ $allowFields = $this->checkAllowFields();
+
+ $db = $this->db();
+
+ $db->transaction(function () use ($data, $sequence, $allowFields, $db) {
+ $result = $db->strict(false)
+ ->field($allowFields)
+ ->replace($this->replace)
+ ->sequence($sequence)
+ ->insert($data, true);
+
+ // 获取自动增长主键
+ if ($result) {
+ $pk = $this->getPk();
+
+ if (is_string($pk) && (!isset($this->data[$pk]) || '' == $this->data[$pk])) {
+ unset($this->get[$pk]);
+ $this->data[$pk] = $result;
+ }
+ }
+
+ // 关联写入
+ if (!empty($this->relationWrite)) {
+ $this->autoRelationInsert();
+ }
+ });
+
+ // 标记数据已经存在
+ $this->exists = true;
+ $this->origin = $this->data;
+
+ // 新增回调
+ $this->trigger('AfterInsert');
+
+ return true;
+ }
+
+ /**
+ * 获取当前的更新条件
+ * @access public
+ * @return mixed
+ */
+ public function getWhere()
+ {
+ $pk = $this->getPk();
+
+ if (is_string($pk) && isset($this->origin[$pk])) {
+ $where = [[$pk, '=', $this->origin[$pk]]];
+ $this->key = $this->origin[$pk];
+ } elseif (is_array($pk)) {
+ foreach ($pk as $field) {
+ if (isset($this->origin[$field])) {
+ $where[] = [$field, '=', $this->origin[$field]];
+ }
+ }
+ }
+
+ if (empty($where)) {
+ $where = empty($this->updateWhere) ? null : $this->updateWhere;
+ }
+
+ return $where;
+ }
+
+ /**
+ * 保存多个数据到当前数据对象
+ * @access public
+ * @param iterable $dataSet 数据
+ * @param boolean $replace 是否自动识别更新和写入
+ * @return Collection
+ * @throws \Exception
+ */
+ public function saveAll(iterable $dataSet, bool $replace = true): Collection
+ {
+ $db = $this->db();
+
+ $result = $db->transaction(function () use ($replace, $dataSet) {
+
+ $pk = $this->getPk();
+
+ if (is_string($pk) && $replace) {
+ $auto = true;
+ }
+
+ $result = [];
+
+ $suffix = $this->getSuffix();
+
+ foreach ($dataSet as $key => $data) {
+ if ($this->exists || (!empty($auto) && isset($data[$pk]))) {
+ $result[$key] = static::update($data, [], [], $suffix);
+ } else {
+ $result[$key] = static::create($data, $this->field, $this->replace, $suffix);
+ }
+ }
+
+ return $result;
+ });
+
+ return $this->toCollection($result);
+ }
+
+ /**
+ * 删除当前的记录
+ * @access public
+ * @return bool
+ */
+ public function delete(): bool
+ {
+ if (!$this->exists || $this->isEmpty() || false === $this->trigger('BeforeDelete')) {
+ return false;
+ }
+
+ // 读取更新条件
+ $where = $this->getWhere();
+
+ $db = $this->db();
+
+ $db->transaction(function () use ($where, $db) {
+ // 删除当前模型数据
+ $db->where($where)->delete();
+
+ // 关联删除
+ if (!empty($this->relationWrite)) {
+ $this->autoRelationDelete();
+ }
+ });
+
+ $this->trigger('AfterDelete');
+
+ $this->exists = false;
+ $this->lazySave = false;
+
+ return true;
+ }
+
+ /**
+ * 写入数据
+ * @access public
+ * @param array $data 数据数组
+ * @param array $allowField 允许字段
+ * @param bool $replace 使用Replace
+ * @param string $suffix 数据表后缀
+ * @return static
+ */
+ public static function create(array $data, array $allowField = [], bool $replace = false, string $suffix = ''): Model
+ {
+ $model = new static();
+
+ if (!empty($allowField)) {
+ $model->allowField($allowField);
+ }
+
+ if (!empty($suffix)) {
+ $model->setSuffix($suffix);
+ }
+
+ $model->replace($replace)->save($data);
+
+ return $model;
+ }
+
+ /**
+ * 更新数据
+ * @access public
+ * @param array $data 数据数组
+ * @param mixed $where 更新条件
+ * @param array $allowField 允许字段
+ * @param string $suffix 数据表后缀
+ * @return static
+ */
+ public static function update(array $data, $where = [], array $allowField = [], string $suffix = '')
+ {
+ $model = new static();
+
+ if (!empty($allowField)) {
+ $model->allowField($allowField);
+ }
+
+ if (!empty($where)) {
+ $model->setUpdateWhere($where);
+ }
+
+ if (!empty($suffix)) {
+ $model->setSuffix($suffix);
+ }
+
+ $model->exists(true)->save($data);
+
+ return $model;
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param mixed $data 主键列表 支持闭包查询条件
+ * @param bool $force 是否强制删除
+ * @return bool
+ */
+ public static function destroy($data, bool $force = false): bool
+ {
+ if (empty($data) && 0 !== $data) {
+ return false;
+ }
+
+ $model = new static();
+
+ $query = $model->db();
+
+ if (is_array($data) && key($data) !== 0) {
+ $query->where($data);
+ $data = null;
+ } elseif ($data instanceof \Closure) {
+ $data($query);
+ $data = null;
+ }
+
+ $resultSet = $query->select($data);
+
+ foreach ($resultSet as $result) {
+ $result->force($force)->delete();
+ }
+
+ return true;
+ }
+
+ /**
+ * 解序列化后处理
+ */
+ public function __wakeup()
+ {
+ $this->initialize();
+ }
+
+ /**
+ * 修改器 设置数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @param mixed $value 值
+ * @return void
+ */
+ public function __set(string $name, $value): void
+ {
+ $this->setAttr($name, $value);
+ }
+
+ /**
+ * 获取器 获取数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return mixed
+ */
+ public function __get(string $name)
+ {
+ return $this->getAttr($name);
+ }
+
+ /**
+ * 检测数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return bool
+ */
+ public function __isset(string $name): bool
+ {
+ return !is_null($this->getAttr($name));
+ }
+
+ /**
+ * 销毁数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return void
+ */
+ public function __unset(string $name): void
+ {
+ unset($this->data[$name],
+ $this->get[$name],
+ $this->relation[$name]);
+ }
+
+ // ArrayAccess
+ public function offsetSet($name, $value)
+ {
+ $this->setAttr($name, $value);
+ }
+
+ public function offsetExists($name): bool
+ {
+ return $this->__isset($name);
+ }
+
+ public function offsetUnset($name)
+ {
+ $this->__unset($name);
+ }
+
+ public function offsetGet($name)
+ {
+ return $this->getAttr($name);
+ }
+
+ /**
+ * 设置不使用的全局查询范围
+ * @access public
+ * @param array $scope 不启用的全局查询范围
+ * @return Query
+ */
+ public static function withoutGlobalScope(array $scope = null)
+ {
+ $model = new static();
+
+ return $model->db($scope);
+ }
+
+ /**
+ * 切换后缀进行查询
+ * @access public
+ * @param string $suffix 切换的表后缀
+ * @return Model
+ */
+ public static function suffix(string $suffix)
+ {
+ $model = new static();
+ $model->setSuffix($suffix);
+
+ return $model;
+ }
+
+ /**
+ * 切换数据库连接进行查询
+ * @access public
+ * @param string $connection 数据库连接标识
+ * @return Model
+ */
+ public static function connect(string $connection)
+ {
+ $model = new static();
+ $model->setConnection($connection);
+
+ return $model;
+ }
+
+ public function __call($method, $args)
+ {
+ if (isset(static::$macro[static::class][$method])) {
+ return call_user_func_array(static::$macro[static::class][$method]->bindTo($this, static::class), $args);
+ }
+
+ if ('withattr' == strtolower($method)) {
+ return call_user_func_array([$this, 'withAttribute'], $args);
+ }
+
+ return call_user_func_array([$this->db(), $method], $args);
+ }
+
+ public static function __callStatic($method, $args)
+ {
+ if (isset(static::$macro[static::class][$method])) {
+ return call_user_func_array(static::$macro[static::class][$method]->bindTo(null, static::class), $args);
+ }
+
+ $model = new static();
+
+ return call_user_func_array([$model->db(), $method], $args);
+ }
+
+ /**
+ * 析构方法
+ * @access public
+ */
+ public function __destruct()
+ {
+ if ($this->lazySave) {
+ $this->save();
+ }
+ }
+}
diff --git a/thinkphp/library/think/Paginator.php b/vendor/topthink/think-orm/src/Paginator.php
old mode 100755
new mode 100644
similarity index 65%
rename from thinkphp/library/think/Paginator.php
rename to vendor/topthink/think-orm/src/Paginator.php
index bbe63e2e3..f5d800682
--- a/thinkphp/library/think/Paginator.php
+++ b/vendor/topthink/think-orm/src/Paginator.php
@@ -2,22 +2,30 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: zhangyajun <448901948@qq.com>
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think;
use ArrayAccess;
use ArrayIterator;
+use Closure;
use Countable;
+use DomainException;
use IteratorAggregate;
use JsonSerializable;
+use think\paginator\driver\Bootstrap;
use Traversable;
+/**
+ * 分页基础类
+ * @mixin Collection
+ */
abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable
{
/**
@@ -34,13 +42,13 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
/**
* 当前页
- * @var integer
+ * @var int
*/
protected $currentPage;
/**
* 最后一页
- * @var integer
+ * @var int
*/
protected $lastPage;
@@ -52,7 +60,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
/**
* 每页数量
- * @var integer
+ * @var int
*/
protected $listRows;
@@ -73,7 +81,24 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
'fragment' => '',
];
- public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = [])
+ /**
+ * 获取当前页码
+ * @var Closure
+ */
+ protected static $currentPageResolver;
+
+ /**
+ * 获取当前路径
+ * @var Closure
+ */
+ protected static $currentPathResolver;
+
+ /**
+ * @var Closure
+ */
+ protected static $maker;
+
+ public function __construct($items, int $listRows, int $currentPage = 1, int $total = null, bool $simple = false, array $options = [])
{
$this->options = array_merge($this->options, $options);
@@ -101,20 +126,29 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
/**
* @access public
- * @param $items
- * @param $listRows
- * @param null $currentPage
- * @param null $total
+ * @param mixed $items
+ * @param int $listRows
+ * @param int $currentPage
+ * @param int $total
* @param bool $simple
* @param array $options
* @return Paginator
*/
- public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = [])
+ public static function make($items, int $listRows, int $currentPage = 1, int $total = null, bool $simple = false, array $options = [])
{
- return new static($items, $listRows, $currentPage, $total, $simple, $options);
+ if (isset(static::$maker)) {
+ return call_user_func(static::$maker, $items, $listRows, $currentPage, $total, $simple, $options);
+ }
+
+ return new Bootstrap($items, $listRows, $currentPage, $total, $simple, $options);
}
- protected function setCurrentPage($currentPage)
+ public static function maker(Closure $resolver)
+ {
+ static::$maker = $resolver;
+ }
+
+ protected function setCurrentPage(int $currentPage): int
{
if (!$this->simple && $currentPage > $this->lastPage) {
return $this->lastPage > 0 ? $this->lastPage : 1;
@@ -127,10 +161,10 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
* 获取页码对应的链接
*
* @access protected
- * @param $page
+ * @param int $page
* @return string
*/
- protected function url($page)
+ protected function url(int $page): string
{
if ($page <= 0) {
$page = 1;
@@ -150,7 +184,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
$url = $path;
if (!empty($parameters)) {
- $url .= '?' . http_build_query($parameters, null, '&');
+ $url .= '?' . http_build_query($parameters, '', '&');
}
return $url . $this->buildFragment();
@@ -159,54 +193,91 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
/**
* 自动获取当前页码
* @access public
- * @param string $varPage
- * @param int $default
+ * @param string $varPage
+ * @param int $default
* @return int
*/
- public static function getCurrentPage($varPage = 'page', $default = 1)
+ public static function getCurrentPage(string $varPage = 'page', int $default = 1): int
{
- $page = Container::get('request')->param($varPage);
-
- if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) {
- return $page;
+ if (isset(static::$currentPageResolver)) {
+ return call_user_func(static::$currentPageResolver, $varPage);
}
return $default;
}
/**
- * 自动获取当前的path
- * @access public
- * @return string
+ * 设置获取当前页码闭包
+ * @param Closure $resolver
*/
- public static function getCurrentPath()
+ public static function currentPageResolver(Closure $resolver)
{
- return Container::get('request')->baseUrl();
+ static::$currentPageResolver = $resolver;
}
- public function total()
+ /**
+ * 自动获取当前的path
+ * @access public
+ * @param string $default
+ * @return string
+ */
+ public static function getCurrentPath($default = '/'): string
+ {
+ if (isset(static::$currentPathResolver)) {
+ return call_user_func(static::$currentPathResolver);
+ }
+
+ return $default;
+ }
+
+ /**
+ * 设置获取当前路径闭包
+ * @param Closure $resolver
+ */
+ public static function currentPathResolver(Closure $resolver)
+ {
+ static::$currentPathResolver = $resolver;
+ }
+
+ /**
+ * 获取数据总条数
+ * @return int
+ */
+ public function total(): int
{
if ($this->simple) {
- throw new \DomainException('not support total');
+ throw new DomainException('not support total');
}
return $this->total;
}
- public function listRows()
+ /**
+ * 获取每页数量
+ * @return int
+ */
+ public function listRows(): int
{
return $this->listRows;
}
- public function currentPage()
+ /**
+ * 获取当前页页码
+ * @return int
+ */
+ public function currentPage(): int
{
return $this->currentPage;
}
- public function lastPage()
+ /**
+ * 获取最后一页页码
+ * @return int
+ */
+ public function lastPage(): int
{
if ($this->simple) {
- throw new \DomainException('not support last');
+ throw new DomainException('not support last');
}
return $this->lastPage;
@@ -215,9 +286,9 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
/**
* 数据是否足够分页
* @access public
- * @return boolean
+ * @return bool
*/
- public function hasPages()
+ public function hasPages(): bool
{
return !(1 == $this->currentPage && !$this->hasMore);
}
@@ -226,11 +297,11 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
* 创建一组分页链接
*
* @access public
- * @param int $start
- * @param int $end
+ * @param int $start
+ * @param int $end
* @return array
*/
- public function getUrlRange($start, $end)
+ public function getUrlRange(int $start, int $end): array
{
$urls = [];
@@ -245,10 +316,10 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
* 设置URL锚点
*
* @access public
- * @param string|null $fragment
+ * @param string|null $fragment
* @return $this
*/
- public function fragment($fragment)
+ public function fragment(string $fragment = null)
{
$this->options['fragment'] = $fragment;
@@ -259,19 +330,12 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
* 添加URL参数
*
* @access public
- * @param array|string $key
- * @param string|null $value
+ * @param array $append
* @return $this
*/
- public function appends($key, $value = null)
+ public function appends(array $append)
{
- if (!is_array($key)) {
- $queries = [$key => $value];
- } else {
- $queries = $key;
- }
-
- foreach ($queries as $k => $v) {
+ foreach ($append as $k => $v) {
if ($k !== $this->options['var_page']) {
$this->options['query'][$k] = $v;
}
@@ -286,7 +350,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
* @access public
* @return string
*/
- protected function buildFragment()
+ protected function buildFragment(): string
{
return $this->options['fragment'] ? '#' . $this->options['fragment'] : '';
}
@@ -303,12 +367,17 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
return $this->items->all();
}
+ /**
+ * 获取数据集
+ *
+ * @return Collection|\think\model\Collection
+ */
public function getCollection()
{
return $this->items;
}
- public function isEmpty()
+ public function isEmpty(): bool
{
return $this->items->isEmpty();
}
@@ -317,7 +386,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
* 给每个元素执行个回调
*
* @access public
- * @param callable $callback
+ * @param callable $callback
* @return $this
*/
public function each(callable $callback)
@@ -349,7 +418,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
/**
* Whether a offset exists
* @access public
- * @param mixed $offset
+ * @param mixed $offset
* @return bool
*/
public function offsetExists($offset)
@@ -360,7 +429,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
/**
* Offset to retrieve
* @access public
- * @param mixed $offset
+ * @param mixed $offset
* @return mixed
*/
public function offsetGet($offset)
@@ -371,8 +440,8 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
/**
* Offset to set
* @access public
- * @param mixed $offset
- * @param mixed $value
+ * @param mixed $offset
+ * @param mixed $value
*/
public function offsetSet($offset, $value)
{
@@ -382,7 +451,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
/**
* Offset to unset
* @access public
- * @param mixed $offset
+ * @param mixed $offset
* @return void
* @since 5.0.0
*/
@@ -392,9 +461,10 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
}
/**
- * Count elements of an object
+ * 统计数据集条数
+ * @return int
*/
- public function count()
+ public function count(): int
{
return $this->items->count();
}
@@ -404,11 +474,15 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
return (string) $this->render();
}
- public function toArray()
+ /**
+ * 转换为数组
+ * @return array
+ */
+ public function toArray(): array
{
try {
$total = $this->total();
- } catch (\DomainException $e) {
+ } catch (DomainException $e) {
$total = null;
}
@@ -431,11 +505,10 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
public function __call($name, $arguments)
{
- $collection = $this->getCollection();
+ $result = call_user_func_array([$this->items, $name], $arguments);
- $result = call_user_func_array([$collection, $name], $arguments);
-
- if ($result === $collection) {
+ if ($result instanceof Collection) {
+ $this->items = $result;
return $this;
}
diff --git a/vendor/topthink/think-orm/src/db/BaseQuery.php b/vendor/topthink/think-orm/src/db/BaseQuery.php
new file mode 100644
index 000000000..4e9a20daa
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/BaseQuery.php
@@ -0,0 +1,1278 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use think\Collection;
+use think\db\exception\DataNotFoundException;
+use think\db\exception\DbException as Exception;
+use think\db\exception\ModelNotFoundException;
+use think\helper\Str;
+use think\Model;
+use think\Paginator;
+
+/**
+ * 数据查询基础类
+ */
+abstract class BaseQuery
+{
+ use concern\TimeFieldQuery;
+ use concern\AggregateQuery;
+ use concern\ModelRelationQuery;
+ use concern\ResultOperation;
+ use concern\Transaction;
+ use concern\WhereQuery;
+
+ /**
+ * 当前数据库连接对象
+ * @var Connection
+ */
+ protected $connection;
+
+ /**
+ * 当前数据表名称(不含前缀)
+ * @var string
+ */
+ protected $name = '';
+
+ /**
+ * 当前数据表主键
+ * @var string|array
+ */
+ protected $pk;
+
+ /**
+ * 当前数据表自增主键
+ * @var string
+ */
+ protected $autoinc;
+
+ /**
+ * 当前数据表前缀
+ * @var string
+ */
+ protected $prefix = '';
+
+ /**
+ * 当前查询参数
+ * @var array
+ */
+ protected $options = [];
+
+ /**
+ * 架构函数
+ * @access public
+ * @param ConnectionInterface $connection 数据库连接对象
+ */
+ public function __construct(ConnectionInterface $connection)
+ {
+ $this->connection = $connection;
+
+ $this->prefix = $this->connection->getConfig('prefix');
+ }
+
+ /**
+ * 利用__call方法实现一些特殊的Model方法
+ * @access public
+ * @param string $method 方法名称
+ * @param array $args 调用参数
+ * @return mixed
+ * @throws Exception
+ */
+ public function __call(string $method, array $args)
+ {
+ if (strtolower(substr($method, 0, 5)) == 'getby') {
+ // 根据某个字段获取记录
+ $field = Str::snake(substr($method, 5));
+ return $this->where($field, '=', $args[0])->find();
+ } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
+ // 根据某个字段获取记录的某个值
+ $name = Str::snake(substr($method, 10));
+ return $this->where($name, '=', $args[0])->value($args[1]);
+ } elseif (strtolower(substr($method, 0, 7)) == 'whereor') {
+ $name = Str::snake(substr($method, 7));
+ array_unshift($args, $name);
+ return call_user_func_array([$this, 'whereOr'], $args);
+ } elseif (strtolower(substr($method, 0, 5)) == 'where') {
+ $name = Str::snake(substr($method, 5));
+ array_unshift($args, $name);
+ return call_user_func_array([$this, 'where'], $args);
+ } elseif ($this->model && method_exists($this->model, 'scope' . $method)) {
+ // 动态调用命名范围
+ $method = 'scope' . $method;
+ array_unshift($args, $this);
+
+ call_user_func_array([$this->model, $method], $args);
+ return $this;
+ } else {
+ throw new Exception('method not exist:' . static::class . '->' . $method);
+ }
+ }
+
+ /**
+ * 创建一个新的查询对象
+ * @access public
+ * @return BaseQuery
+ */
+ public function newQuery(): BaseQuery
+ {
+ $query = new static($this->connection);
+
+ if ($this->model) {
+ $query->model($this->model);
+ }
+
+ if (isset($this->options['table'])) {
+ $query->table($this->options['table']);
+ } else {
+ $query->name($this->name);
+ }
+
+ if (isset($this->options['json'])) {
+ $query->json($this->options['json'], $this->options['json_assoc']);
+ }
+
+ if (isset($this->options['field_type'])) {
+ $query->setFieldType($this->options['field_type']);
+ }
+
+ return $query;
+ }
+
+ /**
+ * 获取当前的数据库Connection对象
+ * @access public
+ * @return ConnectionInterface
+ */
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+ /**
+ * 指定当前数据表名(不含前缀)
+ * @access public
+ * @param string $name 不含前缀的数据表名字
+ * @return $this
+ */
+ public function name(string $name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * 获取当前的数据表名称
+ * @access public
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name ?: $this->model->getName();
+ }
+
+ /**
+ * 获取数据库的配置参数
+ * @access public
+ * @param string $name 参数名称
+ * @return mixed
+ */
+ public function getConfig(string $name = '')
+ {
+ return $this->connection->getConfig($name);
+ }
+
+ /**
+ * 得到当前或者指定名称的数据表
+ * @access public
+ * @param string $name 不含前缀的数据表名字
+ * @return mixed
+ */
+ public function getTable(string $name = '')
+ {
+ if (empty($name) && isset($this->options['table'])) {
+ return $this->options['table'];
+ }
+
+ $name = $name ?: $this->name;
+
+ return $this->prefix . Str::snake($name);
+ }
+
+ /**
+ * 设置字段类型信息
+ * @access public
+ * @param array $type 字段类型信息
+ * @return $this
+ */
+ public function setFieldType(array $type)
+ {
+ $this->options['field_type'] = $type;
+ return $this;
+ }
+
+ /**
+ * 获取最近一次查询的sql语句
+ * @access public
+ * @return string
+ */
+ public function getLastSql(): string
+ {
+ return $this->connection->getLastSql();
+ }
+
+ /**
+ * 获取返回或者影响的记录数
+ * @access public
+ * @return integer
+ */
+ public function getNumRows(): int
+ {
+ return $this->connection->getNumRows();
+ }
+
+ /**
+ * 获取最近插入的ID
+ * @access public
+ * @param string $sequence 自增序列名
+ * @return mixed
+ */
+ public function getLastInsID(string $sequence = null)
+ {
+ return $this->connection->getLastInsID($this, $sequence);
+ }
+
+ /**
+ * 得到某个字段的值
+ * @access public
+ * @param string $field 字段名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function value(string $field, $default = null)
+ {
+ return $this->connection->value($this, $field, $default);
+ }
+
+ /**
+ * 得到某个列的数组
+ * @access public
+ * @param string|array $field 字段名 多个字段用逗号分隔
+ * @param string $key 索引
+ * @return array
+ */
+ public function column($field, string $key = ''): array
+ {
+ return $this->connection->column($this, $field, $key);
+ }
+
+ /**
+ * 查询SQL组装 union
+ * @access public
+ * @param mixed $union UNION
+ * @param boolean $all 是否适用UNION ALL
+ * @return $this
+ */
+ public function union($union, bool $all = false)
+ {
+ $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION';
+
+ if (is_array($union)) {
+ $this->options['union'] = array_merge($this->options['union'], $union);
+ } else {
+ $this->options['union'][] = $union;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 查询SQL组装 union all
+ * @access public
+ * @param mixed $union UNION数据
+ * @return $this
+ */
+ public function unionAll($union)
+ {
+ return $this->union($union, true);
+ }
+
+ /**
+ * 指定查询字段
+ * @access public
+ * @param mixed $field 字段信息
+ * @return $this
+ */
+ public function field($field)
+ {
+ if (empty($field)) {
+ return $this;
+ } elseif ($field instanceof Raw) {
+ $this->options['field'][] = $field;
+ return $this;
+ }
+
+ if (is_string($field)) {
+ if (preg_match('/[\<\'\"\(]/', $field)) {
+ return $this->fieldRaw($field);
+ }
+
+ $field = array_map('trim', explode(',', $field));
+ }
+
+ if (true === $field) {
+ // 获取全部字段
+ $fields = $this->getTableFields();
+ $field = $fields ?: ['*'];
+ }
+
+ if (isset($this->options['field'])) {
+ $field = array_merge((array) $this->options['field'], $field);
+ }
+
+ $this->options['field'] = array_unique($field);
+
+ return $this;
+ }
+
+ /**
+ * 指定要排除的查询字段
+ * @access public
+ * @param array|string $field 要排除的字段
+ * @return $this
+ */
+ public function withoutField($field)
+ {
+ if (empty($field)) {
+ return $this;
+ }
+
+ if (is_string($field)) {
+ $field = array_map('trim', explode(',', $field));
+ }
+
+ // 字段排除
+ $fields = $this->getTableFields();
+ $field = $fields ? array_diff($fields, $field) : $field;
+
+ if (isset($this->options['field'])) {
+ $field = array_merge((array) $this->options['field'], $field);
+ }
+
+ $this->options['field'] = array_unique($field);
+
+ return $this;
+ }
+
+ /**
+ * 指定其它数据表的查询字段
+ * @access public
+ * @param mixed $field 字段信息
+ * @param string $tableName 数据表名
+ * @param string $prefix 字段前缀
+ * @param string $alias 别名前缀
+ * @return $this
+ */
+ public function tableField($field, string $tableName, string $prefix = '', string $alias = '')
+ {
+ if (empty($field)) {
+ return $this;
+ }
+
+ if (is_string($field)) {
+ $field = array_map('trim', explode(',', $field));
+ }
+
+ if (true === $field) {
+ // 获取全部字段
+ $fields = $this->getTableFields($tableName);
+ $field = $fields ?: ['*'];
+ }
+
+ // 添加统一的前缀
+ $prefix = $prefix ?: $tableName;
+ foreach ($field as $key => &$val) {
+ if (is_numeric($key) && $alias) {
+ $field[$prefix . '.' . $val] = $alias . $val;
+ unset($field[$key]);
+ } elseif (is_numeric($key)) {
+ $val = $prefix . '.' . $val;
+ }
+ }
+
+ if (isset($this->options['field'])) {
+ $field = array_merge((array) $this->options['field'], $field);
+ }
+
+ $this->options['field'] = array_unique($field);
+
+ return $this;
+ }
+
+ /**
+ * 设置数据
+ * @access public
+ * @param array $data 数据
+ * @return $this
+ */
+ public function data(array $data)
+ {
+ $this->options['data'] = $data;
+
+ return $this;
+ }
+
+ /**
+ * 去除查询参数
+ * @access public
+ * @param string $option 参数名 留空去除所有参数
+ * @return $this
+ */
+ public function removeOption(string $option = '')
+ {
+ if ('' === $option) {
+ $this->options = [];
+ $this->bind = [];
+ } elseif (isset($this->options[$option])) {
+ unset($this->options[$option]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 指定查询数量
+ * @access public
+ * @param int $offset 起始位置
+ * @param int $length 查询数量
+ * @return $this
+ */
+ public function limit(int $offset, int $length = null)
+ {
+ $this->options['limit'] = $offset . ($length ? ',' . $length : '');
+
+ return $this;
+ }
+
+ /**
+ * 指定分页
+ * @access public
+ * @param int $page 页数
+ * @param int $listRows 每页数量
+ * @return $this
+ */
+ public function page(int $page, int $listRows = null)
+ {
+ $this->options['page'] = [$page, $listRows];
+
+ return $this;
+ }
+
+ /**
+ * 指定当前操作的数据表
+ * @access public
+ * @param mixed $table 表名
+ * @return $this
+ */
+ public function table($table)
+ {
+ if (is_string($table)) {
+ if (strpos($table, ')')) {
+ // 子查询
+ } elseif (false === strpos($table, ',')) {
+ if (strpos($table, ' ')) {
+ [$item, $alias] = explode(' ', $table);
+ $table = [];
+ $this->alias([$item => $alias]);
+ $table[$item] = $alias;
+ }
+ } else {
+ $tables = explode(',', $table);
+ $table = [];
+
+ foreach ($tables as $item) {
+ $item = trim($item);
+ if (strpos($item, ' ')) {
+ [$item, $alias] = explode(' ', $item);
+ $this->alias([$item => $alias]);
+ $table[$item] = $alias;
+ } else {
+ $table[] = $item;
+ }
+ }
+ }
+ } elseif (is_array($table)) {
+ $tables = $table;
+ $table = [];
+
+ foreach ($tables as $key => $val) {
+ if (is_numeric($key)) {
+ $table[] = $val;
+ } else {
+ $this->alias([$key => $val]);
+ $table[$key] = $val;
+ }
+ }
+ }
+
+ $this->options['table'] = $table;
+
+ return $this;
+ }
+
+ /**
+ * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc'])
+ * @access public
+ * @param string|array|Raw $field 排序字段
+ * @param string $order 排序
+ * @return $this
+ */
+ public function order($field, string $order = '')
+ {
+ if (empty($field)) {
+ return $this;
+ } elseif ($field instanceof Raw) {
+ $this->options['order'][] = $field;
+ return $this;
+ }
+
+ if (is_string($field)) {
+ if (!empty($this->options['via'])) {
+ $field = $this->options['via'] . '.' . $field;
+ }
+ if (strpos($field, ',')) {
+ $field = array_map('trim', explode(',', $field));
+ } else {
+ $field = empty($order) ? $field : [$field => $order];
+ }
+ } elseif (!empty($this->options['via'])) {
+ foreach ($field as $key => $val) {
+ if (is_numeric($key)) {
+ $field[$key] = $this->options['via'] . '.' . $val;
+ } else {
+ $field[$this->options['via'] . '.' . $key] = $val;
+ unset($field[$key]);
+ }
+ }
+ }
+
+ if (!isset($this->options['order'])) {
+ $this->options['order'] = [];
+ }
+
+ if (is_array($field)) {
+ $this->options['order'] = array_merge($this->options['order'], $field);
+ } else {
+ $this->options['order'][] = $field;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 分页查询
+ * @access public
+ * @param int|array $listRows 每页数量 数组表示配置参数
+ * @param int|bool $simple 是否简洁模式或者总记录数
+ * @return Paginator
+ * @throws Exception
+ */
+ public function paginate($listRows = null, $simple = false): Paginator
+ {
+ if (is_int($simple)) {
+ $total = $simple;
+ $simple = false;
+ }
+
+ $defaultConfig = [
+ 'query' => [], //url额外参数
+ 'fragment' => '', //url锚点
+ 'var_page' => 'page', //分页变量
+ 'list_rows' => 15, //每页数量
+ ];
+
+ if (is_array($listRows)) {
+ $config = array_merge($defaultConfig, $listRows);
+ $listRows = intval($config['list_rows']);
+ } else {
+ $config = $defaultConfig;
+ $listRows = intval($listRows ?: $config['list_rows']);
+ }
+
+ $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']);
+
+ $page = $page < 1 ? 1 : $page;
+
+ $config['path'] = $config['path'] ?? Paginator::getCurrentPath();
+
+ if (!isset($total) && !$simple) {
+ $options = $this->getOptions();
+
+ unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
+
+ $bind = $this->bind;
+ $total = $this->count();
+ $results = $this->options($options)->bind($bind)->page($page, $listRows)->select();
+ } elseif ($simple) {
+ $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
+ $total = null;
+ } else {
+ $results = $this->page($page, $listRows)->select();
+ }
+
+ $this->removeOption('limit');
+ $this->removeOption('page');
+
+ return Paginator::make($results, $listRows, $page, $total, $simple, $config);
+ }
+
+ /**
+ * 根据数字类型字段进行分页查询(大数据)
+ * @access public
+ * @param int|array $listRows 每页数量或者分页配置
+ * @param string $key 分页索引键
+ * @param string $sort 索引键排序 asc|desc
+ * @return Paginator
+ * @throws Exception
+ */
+ public function paginateX($listRows = null, string $key = null, string $sort = null): Paginator
+ {
+ $defaultConfig = [
+ 'query' => [], //url额外参数
+ 'fragment' => '', //url锚点
+ 'var_page' => 'page', //分页变量
+ 'list_rows' => 15, //每页数量
+ ];
+
+ $config = is_array($listRows) ? array_merge($defaultConfig, $listRows) : $defaultConfig;
+ $listRows = is_int($listRows) ? $listRows : (int) $config['list_rows'];
+ $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']);
+ $page = $page < 1 ? 1 : $page;
+
+ $config['path'] = $config['path'] ?? Paginator::getCurrentPath();
+
+ $key = $key ?: $this->getPk();
+ $options = $this->getOptions();
+
+ if (is_null($sort)) {
+ $order = $options['order'] ?? '';
+ if (!empty($order)) {
+ $sort = $order[$key] ?? 'desc';
+ } else {
+ $this->order($key, 'desc');
+ $sort = 'desc';
+ }
+ } else {
+ $this->order($key, $sort);
+ }
+
+ $newOption = $options;
+ unset($newOption['field'], $newOption['page']);
+
+ $data = $this->newQuery()
+ ->options($newOption)
+ ->field($key)
+ ->where(true)
+ ->order($key, $sort)
+ ->limit(1)
+ ->find();
+
+ $result = $data[$key];
+
+ if (is_numeric($result)) {
+ $lastId = 'asc' == $sort ? ($result - 1) + ($page - 1) * $listRows : ($result + 1) - ($page - 1) * $listRows;
+ } else {
+ throw new Exception('not support type');
+ }
+
+ $results = $this->when($lastId, function ($query) use ($key, $sort, $lastId) {
+ $query->where($key, 'asc' == $sort ? '>' : '<', $lastId);
+ })
+ ->limit($listRows)
+ ->select();
+
+ $this->options($options);
+
+ return Paginator::make($results, $listRows, $page, null, true, $config);
+ }
+
+ /**
+ * 根据最后ID查询更多N个数据
+ * @access public
+ * @param int $limit LIMIT
+ * @param int|string $lastId LastId
+ * @param string $key 分页索引键 默认为主键
+ * @param string $sort 索引键排序 asc|desc
+ * @return array
+ * @throws Exception
+ */
+ public function more(int $limit, $lastId = null, string $key = null, string $sort = null): array
+ {
+ $key = $key ?: $this->getPk();
+
+ if (is_null($sort)) {
+ $order = $this->getOptions('order');
+ if (!empty($order)) {
+ $sort = $order[$key] ?? 'desc';
+ } else {
+ $this->order($key, 'desc');
+ $sort = 'desc';
+ }
+ } else {
+ $this->order($key, $sort);
+ }
+
+ $result = $this->when($lastId, function ($query) use ($key, $sort, $lastId) {
+ $query->where($key, 'asc' == $sort ? '>' : '<', $lastId);
+ })->limit($limit)->select();
+
+ $last = $result->last();
+
+ $result->first();
+
+ return [
+ 'data' => $result,
+ 'lastId' => $last[$key],
+ ];
+ }
+
+ /**
+ * 查询缓存
+ * @access public
+ * @param mixed $key 缓存key
+ * @param integer|\DateTime $expire 缓存有效期
+ * @param string|array $tag 缓存标签
+ * @return $this
+ */
+ public function cache($key = true, $expire = null, $tag = null)
+ {
+ if (false === $key || !$this->getConnection()->getCache()) {
+ return $this;
+ }
+
+ if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) {
+ $expire = $key;
+ $key = true;
+ }
+
+ $this->options['cache'] = [$key, $expire, $tag];
+
+ return $this;
+ }
+
+ /**
+ * 指定查询lock
+ * @access public
+ * @param bool|string $lock 是否lock
+ * @return $this
+ */
+ public function lock($lock = false)
+ {
+ $this->options['lock'] = $lock;
+
+ if ($lock) {
+ $this->options['master'] = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 指定数据表别名
+ * @access public
+ * @param array|string $alias 数据表别名
+ * @return $this
+ */
+ public function alias($alias)
+ {
+ if (is_array($alias)) {
+ $this->options['alias'] = $alias;
+ } else {
+ $table = $this->getTable();
+
+ $this->options['alias'][$table] = $alias;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置从主服务器读取数据
+ * @access public
+ * @param bool $readMaster 是否从主服务器读取
+ * @return $this
+ */
+ public function master(bool $readMaster = true)
+ {
+ $this->options['master'] = $readMaster;
+ return $this;
+ }
+
+ /**
+ * 设置是否严格检查字段名
+ * @access public
+ * @param bool $strict 是否严格检查字段
+ * @return $this
+ */
+ public function strict(bool $strict = true)
+ {
+ $this->options['strict'] = $strict;
+ return $this;
+ }
+
+ /**
+ * 设置自增序列名
+ * @access public
+ * @param string $sequence 自增序列名
+ * @return $this
+ */
+ public function sequence(string $sequence = null)
+ {
+ $this->options['sequence'] = $sequence;
+ return $this;
+ }
+
+ /**
+ * 设置JSON字段信息
+ * @access public
+ * @param array $json JSON字段
+ * @param bool $assoc 是否取出数组
+ * @return $this
+ */
+ public function json(array $json = [], bool $assoc = false)
+ {
+ $this->options['json'] = $json;
+ $this->options['json_assoc'] = $assoc;
+ return $this;
+ }
+
+ /**
+ * 指定数据表主键
+ * @access public
+ * @param string|array $pk 主键
+ * @return $this
+ */
+ public function pk($pk)
+ {
+ $this->pk = $pk;
+ return $this;
+ }
+
+ /**
+ * 查询参数批量赋值
+ * @access protected
+ * @param array $options 表达式参数
+ * @return $this
+ */
+ protected function options(array $options)
+ {
+ $this->options = $options;
+ return $this;
+ }
+
+ /**
+ * 获取当前的查询参数
+ * @access public
+ * @param string $name 参数名
+ * @return mixed
+ */
+ public function getOptions(string $name = '')
+ {
+ if ('' === $name) {
+ return $this->options;
+ }
+
+ return $this->options[$name] ?? null;
+ }
+
+ /**
+ * 设置当前的查询参数
+ * @access public
+ * @param string $option 参数名
+ * @param mixed $value 参数值
+ * @return $this
+ */
+ public function setOption(string $option, $value)
+ {
+ $this->options[$option] = $value;
+ return $this;
+ }
+
+ /**
+ * 设置当前字段添加的表别名
+ * @access public
+ * @param string $via 临时表别名
+ * @return $this
+ */
+ public function via(string $via = '')
+ {
+ $this->options['via'] = $via;
+
+ return $this;
+ }
+
+ /**
+ * 保存记录 自动判断insert或者update
+ * @access public
+ * @param array $data 数据
+ * @param bool $forceInsert 是否强制insert
+ * @return integer
+ */
+ public function save(array $data = [], bool $forceInsert = false)
+ {
+ if ($forceInsert) {
+ return $this->insert($data);
+ }
+
+ $this->options['data'] = array_merge($this->options['data'] ?? [], $data);
+
+ if (!empty($this->options['where'])) {
+ $isUpdate = true;
+ } else {
+ $isUpdate = $this->parseUpdateData($this->options['data']);
+ }
+
+ return $isUpdate ? $this->update() : $this->insert();
+ }
+
+ /**
+ * 插入记录
+ * @access public
+ * @param array $data 数据
+ * @param boolean $getLastInsID 返回自增主键
+ * @return integer|string
+ */
+ public function insert(array $data = [], bool $getLastInsID = false)
+ {
+ if (!empty($data)) {
+ $this->options['data'] = $data;
+ }
+
+ return $this->connection->insert($this, $getLastInsID);
+ }
+
+ /**
+ * 插入记录并获取自增ID
+ * @access public
+ * @param array $data 数据
+ * @return integer|string
+ */
+ public function insertGetId(array $data)
+ {
+ return $this->insert($data, true);
+ }
+
+ /**
+ * 批量插入记录
+ * @access public
+ * @param array $dataSet 数据集
+ * @param integer $limit 每次写入数据限制
+ * @return integer
+ */
+ public function insertAll(array $dataSet = [], int $limit = 0): int
+ {
+ if (empty($dataSet)) {
+ $dataSet = $this->options['data'] ?? [];
+ }
+
+ if (empty($limit) && !empty($this->options['limit']) && is_numeric($this->options['limit'])) {
+ $limit = (int) $this->options['limit'];
+ }
+
+ return $this->connection->insertAll($this, $dataSet, $limit);
+ }
+
+ /**
+ * 通过Select方式插入记录
+ * @access public
+ * @param array $fields 要插入的数据表字段名
+ * @param string $table 要插入的数据表名
+ * @return integer
+ */
+ public function selectInsert(array $fields, string $table): int
+ {
+ return $this->connection->selectInsert($this, $fields, $table);
+ }
+
+ /**
+ * 更新记录
+ * @access public
+ * @param mixed $data 数据
+ * @return integer
+ * @throws Exception
+ */
+ public function update(array $data = []): int
+ {
+ if (!empty($data)) {
+ $this->options['data'] = array_merge($this->options['data'] ?? [], $data);
+ }
+
+ if (empty($this->options['where'])) {
+ $this->parseUpdateData($this->options['data']);
+ }
+
+ if (empty($this->options['where']) && $this->model) {
+ $this->where($this->model->getWhere());
+ }
+
+ if (empty($this->options['where'])) {
+ // 如果没有任何更新条件则不执行
+ throw new Exception('miss update condition');
+ }
+
+ return $this->connection->update($this);
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param mixed $data 表达式 true 表示强制删除
+ * @return int
+ * @throws Exception
+ */
+ public function delete($data = null): int
+ {
+ if (!is_null($data) && true !== $data) {
+ // AR模式分析主键条件
+ $this->parsePkWhere($data);
+ }
+
+ if (empty($this->options['where']) && $this->model) {
+ $this->where($this->model->getWhere());
+ }
+
+ if (true !== $data && empty($this->options['where'])) {
+ // 如果条件为空 不进行删除操作 除非设置 1=1
+ throw new Exception('delete without condition');
+ }
+
+ if (!empty($this->options['soft_delete'])) {
+ // 软删除
+ list($field, $condition) = $this->options['soft_delete'];
+ if ($condition) {
+ unset($this->options['soft_delete']);
+ $this->options['data'] = [$field => $condition];
+
+ return $this->connection->update($this);
+ }
+ }
+
+ $this->options['data'] = $data;
+
+ return $this->connection->delete($this);
+ }
+
+ /**
+ * 查找记录
+ * @access public
+ * @param mixed $data 数据
+ * @return Collection
+ * @throws Exception
+ * @throws ModelNotFoundException
+ * @throws DataNotFoundException
+ */
+ public function select($data = null): Collection
+ {
+ if (!is_null($data)) {
+ // 主键条件分析
+ $this->parsePkWhere($data);
+ }
+
+ $resultSet = $this->connection->select($this);
+
+ // 返回结果处理
+ if (!empty($this->options['fail']) && count($resultSet) == 0) {
+ $this->throwNotFound();
+ }
+
+ // 数据列表读取后的处理
+ if (!empty($this->model)) {
+ // 生成模型对象
+ $resultSet = $this->resultSetToModelCollection($resultSet);
+ } else {
+ $this->resultSet($resultSet);
+ }
+
+ return $resultSet;
+ }
+
+ /**
+ * 查找单条记录
+ * @access public
+ * @param mixed $data 查询数据
+ * @return array|Model|null
+ * @throws Exception
+ * @throws ModelNotFoundException
+ * @throws DataNotFoundException
+ */
+ public function find($data = null)
+ {
+ if (!is_null($data)) {
+ // AR模式分析主键条件
+ $this->parsePkWhere($data);
+ }
+
+ if (empty($this->options['where']) && empty($this->options['order'])) {
+ $result = [];
+ } else {
+ $result = $this->connection->find($this);
+ }
+
+ // 数据处理
+ if (empty($result)) {
+ return $this->resultToEmpty();
+ }
+
+ if (!empty($this->model)) {
+ // 返回模型对象
+ $this->resultToModel($result, $this->options);
+ } else {
+ $this->result($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 分析表达式(可用于查询或者写入操作)
+ * @access public
+ * @return array
+ */
+ public function parseOptions(): array
+ {
+ $options = $this->getOptions();
+
+ // 获取数据表
+ if (empty($options['table'])) {
+ $options['table'] = $this->getTable();
+ }
+
+ if (!isset($options['where'])) {
+ $options['where'] = [];
+ } elseif (isset($options['view'])) {
+ // 视图查询条件处理
+ $this->parseView($options);
+ }
+
+ foreach (['data', 'order', 'join', 'union'] as $name) {
+ if (!isset($options[$name])) {
+ $options[$name] = [];
+ }
+ }
+
+ if (!isset($options['strict'])) {
+ $options['strict'] = $this->connection->getConfig('fields_strict');
+ }
+
+ foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure'] as $name) {
+ if (!isset($options[$name])) {
+ $options[$name] = false;
+ }
+ }
+
+ foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) {
+ if (!isset($options[$name])) {
+ $options[$name] = '';
+ }
+ }
+
+ if (isset($options['page'])) {
+ // 根据页数计算limit
+ [$page, $listRows] = $options['page'];
+ $page = $page > 0 ? $page : 1;
+ $listRows = $listRows ?: (is_numeric($options['limit']) ? $options['limit'] : 20);
+ $offset = $listRows * ($page - 1);
+ $options['limit'] = $offset . ',' . $listRows;
+ }
+
+ $this->options = $options;
+
+ return $options;
+ }
+
+ /**
+ * 分析数据是否存在更新条件
+ * @access public
+ * @param array $data 数据
+ * @return bool
+ * @throws Exception
+ */
+ public function parseUpdateData(&$data): bool
+ {
+ $pk = $this->getPk();
+ $isUpdate = false;
+ // 如果存在主键数据 则自动作为更新条件
+ if (is_string($pk) && isset($data[$pk])) {
+ $this->where($pk, '=', $data[$pk]);
+ $this->options['key'] = $data[$pk];
+ unset($data[$pk]);
+ $isUpdate = true;
+ } elseif (is_array($pk)) {
+ foreach ($pk as $field) {
+ if (isset($data[$field])) {
+ $this->where($field, '=', $data[$field]);
+ $isUpdate = true;
+ } else {
+ // 如果缺少复合主键数据则不执行
+ throw new Exception('miss complex primary data');
+ }
+ unset($data[$field]);
+ }
+ }
+
+ return $isUpdate;
+ }
+
+ /**
+ * 把主键值转换为查询条件 支持复合主键
+ * @access public
+ * @param array|string $data 主键数据
+ * @return void
+ * @throws Exception
+ */
+ public function parsePkWhere($data): void
+ {
+ $pk = $this->getPk();
+
+ if (is_string($pk)) {
+ // 获取数据表
+ if (empty($this->options['table'])) {
+ $this->options['table'] = $this->getTable();
+ }
+
+ $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table'];
+
+ if (!empty($this->options['alias'][$table])) {
+ $alias = $this->options['alias'][$table];
+ }
+
+ $key = isset($alias) ? $alias . '.' . $pk : $pk;
+ // 根据主键查询
+ if (is_array($data)) {
+ $this->where($key, 'in', $data);
+ } else {
+ $this->where($key, '=', $data);
+ $this->options['key'] = $data;
+ }
+ }
+ }
+
+ /**
+ * 获取模型的更新条件
+ * @access protected
+ * @param array $options 查询参数
+ */
+ protected function getModelUpdateCondition(array $options)
+ {
+ return $options['where']['AND'] ?? null;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/Builder.php b/vendor/topthink/think-orm/src/db/Builder.php
new file mode 100644
index 000000000..73244f4d7
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/Builder.php
@@ -0,0 +1,1305 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use Closure;
+use PDO;
+use think\db\exception\DbException as Exception;
+
+/**
+ * Db Builder
+ */
+abstract class Builder
+{
+ /**
+ * Connection对象
+ * @var ConnectionInterface
+ */
+ protected $connection;
+
+ /**
+ * 查询表达式映射
+ * @var array
+ */
+ protected $exp = ['NOTLIKE' => 'NOT LIKE', 'NOTIN' => 'NOT IN', 'NOTBETWEEN' => 'NOT BETWEEN', 'NOTEXISTS' => 'NOT EXISTS', 'NOTNULL' => 'NOT NULL', 'NOTBETWEEN TIME' => 'NOT BETWEEN TIME'];
+
+ /**
+ * 查询表达式解析
+ * @var array
+ */
+ protected $parser = [
+ 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='],
+ 'parseLike' => ['LIKE', 'NOT LIKE'],
+ 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'],
+ 'parseIn' => ['NOT IN', 'IN'],
+ 'parseExp' => ['EXP'],
+ 'parseNull' => ['NOT NULL', 'NULL'],
+ 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'],
+ 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'],
+ 'parseExists' => ['NOT EXISTS', 'EXISTS'],
+ 'parseColumn' => ['COLUMN'],
+ ];
+
+ /**
+ * SELECT SQL表达式
+ * @var string
+ */
+ protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * INSERT SQL表达式
+ * @var string
+ */
+ protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
+
+ /**
+ * INSERT ALL SQL表达式
+ * @var string
+ */
+ protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%';
+
+ /**
+ * UPDATE SQL表达式
+ * @var string
+ */
+ protected $updateSql = 'UPDATE%EXTRA% %TABLE% SET %SET%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * DELETE SQL表达式
+ * @var string
+ */
+ protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * 架构函数
+ * @access public
+ * @param ConnectionInterface $connection 数据库连接对象实例
+ */
+ public function __construct(ConnectionInterface $connection)
+ {
+ $this->connection = $connection;
+ }
+
+ /**
+ * 获取当前的连接对象实例
+ * @access public
+ * @return ConnectionInterface
+ */
+ public function getConnection(): ConnectionInterface
+ {
+ return $this->connection;
+ }
+
+ /**
+ * 注册查询表达式解析
+ * @access public
+ * @param string $name 解析方法
+ * @param array $parser 匹配表达式数据
+ * @return $this
+ */
+ public function bindParser(string $name, array $parser)
+ {
+ $this->parser[$name] = $parser;
+ return $this;
+ }
+
+ /**
+ * 数据分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param array $data 数据
+ * @param array $fields 字段信息
+ * @param array $bind 参数绑定
+ * @return array
+ */
+ protected function parseData(Query $query, array $data = [], array $fields = [], array $bind = []): array
+ {
+ if (empty($data)) {
+ return [];
+ }
+
+ $options = $query->getOptions();
+
+ // 获取绑定信息
+ if (empty($bind)) {
+ $bind = $query->getFieldsBindType();
+ }
+
+ if (empty($fields)) {
+ if (empty($options['field']) || '*' == $options['field']) {
+ $fields = array_keys($bind);
+ } else {
+ $fields = $options['field'];
+ }
+ }
+
+ $result = [];
+
+ foreach ($data as $key => $val) {
+ $item = $this->parseKey($query, $key, true);
+
+ if ($val instanceof Raw) {
+ $result[$item] = $this->parseRaw($query, $val);
+ continue;
+ } elseif (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $query->getFieldType($key))) {
+ $val = json_encode($val);
+ }
+
+ if (false !== strpos($key, '->')) {
+ [$key, $name] = explode('->', $key, 2);
+ $item = $this->parseKey($query, $key);
+ $result[$item] = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key . '->' . $name, $val, $bind) . ')';
+ } elseif (false === strpos($key, '.') && !in_array($key, $fields, true)) {
+ if ($options['strict']) {
+ throw new Exception('fields not exists:[' . $key . ']');
+ }
+ } elseif (is_null($val)) {
+ $result[$item] = 'NULL';
+ } elseif (is_array($val) && !empty($val) && is_string($val[0])) {
+ switch (strtoupper($val[0])) {
+ case 'INC':
+ $result[$item] = $item . ' + ' . floatval($val[1]);
+ break;
+ case 'DEC':
+ $result[$item] = $item . ' - ' . floatval($val[1]);
+ break;
+ }
+ } elseif (is_scalar($val)) {
+ // 过滤非标量数据
+ $result[$item] = $this->parseDataBind($query, $key, $val, $bind);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 数据绑定处理
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key 字段名
+ * @param mixed $data 数据
+ * @param array $bind 绑定数据
+ * @return string
+ */
+ protected function parseDataBind(Query $query, string $key, $data, array $bind = []): string
+ {
+ if ($data instanceof Raw) {
+ return $this->parseRaw($query, $data);
+ }
+
+ $name = $query->bindValue($data, $bind[$key] ?? PDO::PARAM_STR);
+
+ return ':' . $name;
+ }
+
+ /**
+ * 字段名分析
+ * @access public
+ * @param Query $query 查询对象
+ * @param mixed $key 字段名
+ * @param bool $strict 严格检测
+ * @return string
+ */
+ public function parseKey(Query $query, $key, bool $strict = false): string
+ {
+ return $key;
+ }
+
+ /**
+ * 查询额外参数分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $extra 额外参数
+ * @return string
+ */
+ protected function parseExtra(Query $query, string $extra): string
+ {
+ return preg_match('/^[\w]+$/i', $extra) ? ' ' . strtoupper($extra) : '';
+ }
+
+ /**
+ * field分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $fields 字段名
+ * @return string
+ */
+ protected function parseField(Query $query, $fields): string
+ {
+ if (is_array($fields)) {
+ // 支持 'field1'=>'field2' 这样的字段别名定义
+ $array = [];
+
+ foreach ($fields as $key => $field) {
+ if ($field instanceof Raw) {
+ $array[] = $this->parseRaw($query, $field);
+ } elseif (!is_numeric($key)) {
+ $array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field, true);
+ } else {
+ $array[] = $this->parseKey($query, $field);
+ }
+ }
+
+ $fieldsStr = implode(',', $array);
+ } else {
+ $fieldsStr = '*';
+ }
+
+ return $fieldsStr;
+ }
+
+ /**
+ * table分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $tables 表名
+ * @return string
+ */
+ protected function parseTable(Query $query, $tables): string
+ {
+ $item = [];
+ $options = $query->getOptions();
+
+ foreach ((array) $tables as $key => $table) {
+ if ($table instanceof Raw) {
+ $item[] = $this->parseRaw($query, $table);
+ } elseif (!is_numeric($key)) {
+ $item[] = $this->parseKey($query, $key) . ' ' . $this->parseKey($query, $table);
+ } elseif (isset($options['alias'][$table])) {
+ $item[] = $this->parseKey($query, $table) . ' ' . $this->parseKey($query, $options['alias'][$table]);
+ } else {
+ $item[] = $this->parseKey($query, $table);
+ }
+ }
+
+ return implode(',', $item);
+ }
+
+ /**
+ * where分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $where 查询条件
+ * @return string
+ */
+ protected function parseWhere(Query $query, array $where): string
+ {
+ $options = $query->getOptions();
+ $whereStr = $this->buildWhere($query, $where);
+
+ if (!empty($options['soft_delete'])) {
+ // 附加软删除条件
+ [$field, $condition] = $options['soft_delete'];
+
+ $binds = $query->getFieldsBindType();
+ $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : '';
+ $whereStr = $whereStr . $this->parseWhereItem($query, $field, $condition, $binds);
+ }
+
+ return empty($whereStr) ? '' : ' WHERE ' . $whereStr;
+ }
+
+ /**
+ * 生成查询条件SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @param mixed $where 查询条件
+ * @return string
+ */
+ public function buildWhere(Query $query, array $where): string
+ {
+ if (empty($where)) {
+ $where = [];
+ }
+
+ $whereStr = '';
+
+ $binds = $query->getFieldsBindType();
+
+ foreach ($where as $logic => $val) {
+ $str = $this->parseWhereLogic($query, $logic, $val, $binds);
+
+ $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($logic) + 1) : implode(' ', $str);
+ }
+
+ return $whereStr;
+ }
+
+ /**
+ * 不同字段使用相同查询条件(AND)
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $logic Logic
+ * @param array $val 查询条件
+ * @param array $binds 参数绑定
+ * @return array
+ */
+ protected function parseWhereLogic(Query $query, string $logic, array $val, array $binds = []): array
+ {
+ $where = [];
+ foreach ($val as $value) {
+ if ($value instanceof Raw) {
+ $where[] = ' ' . $logic . ' ( ' . $this->parseRaw($query, $value) . ' )';
+ continue;
+ }
+
+ if (is_array($value)) {
+ if (key($value) !== 0) {
+ throw new Exception('where express error:' . var_export($value, true));
+ }
+ $field = array_shift($value);
+ } elseif (true === $value) {
+ $where[] = ' ' . $logic . ' 1 ';
+ continue;
+ } elseif (!($value instanceof Closure)) {
+ throw new Exception('where express error:' . var_export($value, true));
+ }
+
+ if ($value instanceof Closure) {
+ // 使用闭包查询
+ $whereClosureStr = $this->parseClosureWhere($query, $value, $logic);
+ if ($whereClosureStr) {
+ $where[] = $whereClosureStr;
+ }
+ } elseif (is_array($field)) {
+ $where[] = $this->parseMultiWhereField($query, $value, $field, $logic, $binds);
+ } elseif ($field instanceof Raw) {
+ $where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds);
+ } elseif (strpos($field, '|')) {
+ $where[] = $this->parseFieldsOr($query, $value, $field, $logic, $binds);
+ } elseif (strpos($field, '&')) {
+ $where[] = $this->parseFieldsAnd($query, $value, $field, $logic, $binds);
+ } else {
+ // 对字段使用表达式查询
+ $field = is_string($field) ? $field : '';
+ $where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds);
+ }
+ }
+
+ return $where;
+ }
+
+ /**
+ * 不同字段使用相同查询条件(AND)
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $value 查询条件
+ * @param string $field 查询字段
+ * @param string $logic Logic
+ * @param array $binds 参数绑定
+ * @return string
+ */
+ protected function parseFieldsAnd(Query $query, $value, string $field, string $logic, array $binds): string
+ {
+ $item = [];
+
+ foreach (explode('&', $field) as $k) {
+ $item[] = $this->parseWhereItem($query, $k, $value, $binds);
+ }
+
+ return ' ' . $logic . ' ( ' . implode(' AND ', $item) . ' )';
+ }
+
+ /**
+ * 不同字段使用相同查询条件(OR)
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $value 查询条件
+ * @param string $field 查询字段
+ * @param string $logic Logic
+ * @param array $binds 参数绑定
+ * @return string
+ */
+ protected function parseFieldsOr(Query $query, $value, string $field, string $logic, array $binds): string
+ {
+ $item = [];
+
+ foreach (explode('|', $field) as $k) {
+ $item[] = $this->parseWhereItem($query, $k, $value, $binds);
+ }
+
+ return ' ' . $logic . ' ( ' . implode(' OR ', $item) . ' )';
+ }
+
+ /**
+ * 闭包查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param Closure $value 查询条件
+ * @param string $logic Logic
+ * @return string
+ */
+ protected function parseClosureWhere(Query $query, Closure $value, string $logic): string
+ {
+ $newQuery = $query->newQuery();
+ $value($newQuery);
+ $whereClosure = $this->buildWhere($newQuery, $newQuery->getOptions('where') ?: []);
+
+ if (!empty($whereClosure)) {
+ $query->bind($newQuery->getBind(false));
+ $where = ' ' . $logic . ' ( ' . $whereClosure . ' )';
+ }
+
+ return $where ?? '';
+ }
+
+ /**
+ * 复合条件查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $value 查询条件
+ * @param mixed $field 查询字段
+ * @param string $logic Logic
+ * @param array $binds 参数绑定
+ * @return string
+ */
+ protected function parseMultiWhereField(Query $query, $value, $field, string $logic, array $binds): string
+ {
+ array_unshift($value, $field);
+
+ $where = [];
+ foreach ($value as $item) {
+ $where[] = $this->parseWhereItem($query, array_shift($item), $item, $binds);
+ }
+
+ return ' ' . $logic . ' ( ' . implode(' AND ', $where) . ' )';
+ }
+
+ /**
+ * where子单元分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $field 查询字段
+ * @param array $val 查询条件
+ * @param array $binds 参数绑定
+ * @return string
+ */
+ protected function parseWhereItem(Query $query, $field, array $val, array $binds = []): string
+ {
+ // 字段分析
+ $key = $field ? $this->parseKey($query, $field, true) : '';
+
+ [$exp, $value] = $val;
+
+ // 检测操作符
+ if (!is_string($exp)) {
+ throw new Exception('where express error:' . var_export($exp, true));
+ }
+
+ $exp = strtoupper($exp);
+ if (isset($this->exp[$exp])) {
+ $exp = $this->exp[$exp];
+ }
+
+ if (is_string($field) && 'LIKE' != $exp) {
+ $bindType = $binds[$field] ?? PDO::PARAM_STR;
+ } else {
+ $bindType = PDO::PARAM_STR;
+ }
+
+ if ($value instanceof Raw) {
+
+ } elseif (is_object($value) && method_exists($value, '__toString')) {
+ // 对象数据写入
+ $value = $value->__toString();
+ }
+
+ if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) {
+ if (is_string($value) && 0 === strpos($value, ':') && $query->isBind(substr($value, 1))) {
+ } else {
+ $name = $query->bindValue($value, $bindType);
+ $value = ':' . $name;
+ }
+ }
+
+ // 解析查询表达式
+ foreach ($this->parser as $fun => $parse) {
+ if (in_array($exp, $parse)) {
+ return $this->$fun($query, $key, $exp, $value, $field, $bindType, $val[2] ?? 'AND');
+ }
+ }
+
+ throw new Exception('where express error:' . $exp);
+ }
+
+ /**
+ * 模糊查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param array $value
+ * @param string $field
+ * @param integer $bindType
+ * @param string $logic
+ * @return string
+ */
+ protected function parseLike(Query $query, string $key, string $exp, $value, $field, int $bindType, string $logic): string
+ {
+ // 模糊匹配
+ if (is_array($value)) {
+ $array = [];
+ foreach ($value as $item) {
+ $name = $query->bindValue($item, PDO::PARAM_STR);
+ $array[] = $key . ' ' . $exp . ' :' . $name;
+ }
+
+ $whereStr = '(' . implode(' ' . strtoupper($logic) . ' ', $array) . ')';
+ } else {
+ $whereStr = $key . ' ' . $exp . ' ' . $value;
+ }
+
+ return $whereStr;
+ }
+
+ /**
+ * 表达式查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param array $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseExp(Query $query, string $key, string $exp, Raw $value, string $field, int $bindType): string
+ {
+ // 表达式查询
+ return '( ' . $key . ' ' . $this->parseRaw($query, $value) . ' )';
+ }
+
+ /**
+ * 表达式查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param array $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseColumn(Query $query, string $key, $exp, array $value, string $field, int $bindType): string
+ {
+ // 字段比较查询
+ [$op, $field] = $value;
+
+ if (!in_array(trim($op), ['=', '<>', '>', '>=', '<', '<='])) {
+ throw new Exception('where express error:' . var_export($value, true));
+ }
+
+ return '( ' . $key . ' ' . $op . ' ' . $this->parseKey($query, $field, true) . ' )';
+ }
+
+ /**
+ * Null查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseNull(Query $query, string $key, string $exp, $value, $field, int $bindType): string
+ {
+ // NULL 查询
+ return $key . ' IS ' . $exp;
+ }
+
+ /**
+ * 范围查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseBetween(Query $query, string $key, string $exp, $value, $field, int $bindType): string
+ {
+ // BETWEEN 查询
+ $data = is_array($value) ? $value : explode(',', $value);
+
+ $min = $query->bindValue($data[0], $bindType);
+ $max = $query->bindValue($data[1], $bindType);
+
+ return $key . ' ' . $exp . ' :' . $min . ' AND :' . $max . ' ';
+ }
+
+ /**
+ * Exists查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseExists(Query $query, string $key, string $exp, $value, string $field, int $bindType): string
+ {
+ // EXISTS 查询
+ if ($value instanceof Closure) {
+ $value = $this->parseClosure($query, $value, false);
+ } elseif ($value instanceof Raw) {
+ $value = $this->parseRaw($query, $value);
+ } else {
+ throw new Exception('where express error:' . $value);
+ }
+
+ return $exp . ' ( ' . $value . ' )';
+ }
+
+ /**
+ * 时间比较查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string
+ {
+ return $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($query, $value, $field, $bindType);
+ }
+
+ /**
+ * 大小比较查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseCompare(Query $query, string $key, string $exp, $value, $field, int $bindType): string
+ {
+ if (is_array($value)) {
+ throw new Exception('where express error:' . $exp . var_export($value, true));
+ }
+
+ // 比较运算
+ if ($value instanceof Closure) {
+ $value = $this->parseClosure($query, $value);
+ }
+
+ if ('=' == $exp && is_null($value)) {
+ return $key . ' IS NULL';
+ }
+
+ return $key . ' ' . $exp . ' ' . $value;
+ }
+
+ /**
+ * 时间范围查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseBetweenTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string
+ {
+ if (is_string($value)) {
+ $value = explode(',', $value);
+ }
+
+ return $key . ' ' . substr($exp, 0, -4)
+ . $this->parseDateTime($query, $value[0], $field, $bindType)
+ . ' AND '
+ . $this->parseDateTime($query, $value[1], $field, $bindType);
+
+ }
+
+ /**
+ * IN查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseIn(Query $query, string $key, string $exp, $value, $field, int $bindType): string
+ {
+ // IN 查询
+ if ($value instanceof Closure) {
+ $value = $this->parseClosure($query, $value, false);
+ } elseif ($value instanceof Raw) {
+ $value = $this->parseRaw($query, $value);
+ } else {
+ $value = array_unique(is_array($value) ? $value : explode(',', $value));
+ if (count($value) === 0) {
+ return 'IN' == $exp ? '0 = 1' : '1 = 1';
+ }
+ $array = [];
+
+ foreach ($value as $v) {
+ $name = $query->bindValue($v, $bindType);
+ $array[] = ':' . $name;
+ }
+
+ if (count($array) == 1) {
+ return $key . ('IN' == $exp ? ' = ' : ' <> ') . $array[0];
+ } else {
+ $value = implode(',', $array);
+ }
+ }
+
+ return $key . ' ' . $exp . ' (' . $value . ')';
+ }
+
+ /**
+ * 闭包子查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param \Closure $call
+ * @param bool $show
+ * @return string
+ */
+ protected function parseClosure(Query $query, Closure $call, bool $show = true): string
+ {
+ $newQuery = $query->newQuery()->removeOption();
+ $call($newQuery);
+
+ return $newQuery->buildSql($show);
+ }
+
+ /**
+ * 日期时间条件解析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $value
+ * @param string $key
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseDateTime(Query $query, $value, string $key, int $bindType): string
+ {
+ $options = $query->getOptions();
+
+ // 获取时间字段类型
+ if (strpos($key, '.')) {
+ [$table, $key] = explode('.', $key);
+
+ if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) {
+ $table = $pos;
+ }
+ } else {
+ $table = $options['table'];
+ }
+
+ $type = $query->getFieldType($key);
+
+ if ($type) {
+ if (is_string($value)) {
+ $value = strtotime($value) ?: $value;
+ }
+
+ if (is_int($value)) {
+ if (preg_match('/(datetime|timestamp)/is', $type)) {
+ // 日期及时间戳类型
+ $value = date('Y-m-d H:i:s', $value);
+ } elseif (preg_match('/(date)/is', $type)) {
+ // 日期及时间戳类型
+ $value = date('Y-m-d', $value);
+ }
+ }
+ }
+
+ $name = $query->bindValue($value, $bindType);
+
+ return ':' . $name;
+ }
+
+ /**
+ * limit分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $limit
+ * @return string
+ */
+ protected function parseLimit(Query $query, string $limit): string
+ {
+ return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : '';
+ }
+
+ /**
+ * join分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param array $join
+ * @return string
+ */
+ protected function parseJoin(Query $query, array $join): string
+ {
+ $joinStr = '';
+
+ foreach ($join as $item) {
+ [$table, $type, $on] = $item;
+
+ if (strpos($on, '=')) {
+ [$val1, $val2] = explode('=', $on, 2);
+
+ $condition = $this->parseKey($query, $val1) . '=' . $this->parseKey($query, $val2);
+ } else {
+ $condition = $on;
+ }
+
+ $table = $this->parseTable($query, $table);
+
+ $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . $condition;
+ }
+
+ return $joinStr;
+ }
+
+ /**
+ * order分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param array $order
+ * @return string
+ */
+ protected function parseOrder(Query $query, array $order): string
+ {
+ $array = [];
+ foreach ($order as $key => $val) {
+ if ($val instanceof Raw) {
+ $array[] = $this->parseRaw($query, $val);
+ } elseif (is_array($val) && preg_match('/^[\w\.]+$/', $key)) {
+ $array[] = $this->parseOrderField($query, $key, $val);
+ } elseif ('[rand]' == $val) {
+ $array[] = $this->parseRand($query);
+ } elseif (is_string($val)) {
+ if (is_numeric($key)) {
+ [$key, $sort] = explode(' ', strpos($val, ' ') ? $val : $val . ' ');
+ } else {
+ $sort = $val;
+ }
+
+ if (preg_match('/^[\w\.]+$/', $key)) {
+ $sort = strtoupper($sort);
+ $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : '';
+ $array[] = $this->parseKey($query, $key, true) . $sort;
+ } else {
+ throw new Exception('order express error:' . $key);
+ }
+ }
+ }
+
+ return empty($array) ? '' : ' ORDER BY ' . implode(',', $array);
+ }
+
+ /**
+ * 分析Raw对象
+ * @access protected
+ * @param Query $query 查询对象
+ * @param Raw $raw Raw对象
+ * @return string
+ */
+ protected function parseRaw(Query $query, Raw $raw): string
+ {
+ $sql = $raw->getValue();
+ $bind = $raw->getBind();
+
+ if ($bind) {
+ $query->bindParams($sql, $bind);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * 随机排序
+ * @access protected
+ * @param Query $query 查询对象
+ * @return string
+ */
+ protected function parseRand(Query $query): string
+ {
+ return '';
+ }
+
+ /**
+ * orderField分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param array $val
+ * @return string
+ */
+ protected function parseOrderField(Query $query, string $key, array $val): string
+ {
+ if (isset($val['sort'])) {
+ $sort = $val['sort'];
+ unset($val['sort']);
+ } else {
+ $sort = '';
+ }
+
+ $sort = strtoupper($sort);
+ $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : '';
+ $bind = $query->getFieldsBindType();
+
+ foreach ($val as $item) {
+ $val[] = $this->parseDataBind($query, $key, $item, $bind);
+ }
+
+ return 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')' . $sort;
+ }
+
+ /**
+ * group分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $group
+ * @return string
+ */
+ protected function parseGroup(Query $query, $group): string
+ {
+ if (empty($group)) {
+ return '';
+ }
+
+ if (is_string($group)) {
+ $group = explode(',', $group);
+ }
+
+ $val = [];
+ foreach ($group as $key) {
+ $val[] = $this->parseKey($query, $key);
+ }
+
+ return ' GROUP BY ' . implode(',', $val);
+ }
+
+ /**
+ * having分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $having
+ * @return string
+ */
+ protected function parseHaving(Query $query, string $having): string
+ {
+ return !empty($having) ? ' HAVING ' . $having : '';
+ }
+
+ /**
+ * comment分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $comment
+ * @return string
+ */
+ protected function parseComment(Query $query, string $comment): string
+ {
+ if (false !== strpos($comment, '*/')) {
+ $comment = strstr($comment, '*/', true);
+ }
+
+ return !empty($comment) ? ' /* ' . $comment . ' */' : '';
+ }
+
+ /**
+ * distinct分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $distinct
+ * @return string
+ */
+ protected function parseDistinct(Query $query, bool $distinct): string
+ {
+ return !empty($distinct) ? ' DISTINCT ' : '';
+ }
+
+ /**
+ * union分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param array $union
+ * @return string
+ */
+ protected function parseUnion(Query $query, array $union): string
+ {
+ if (empty($union)) {
+ return '';
+ }
+
+ $type = $union['type'];
+ unset($union['type']);
+
+ foreach ($union as $u) {
+ if ($u instanceof Closure) {
+ $sql[] = $type . ' ' . $this->parseClosure($query, $u);
+ } elseif (is_string($u)) {
+ $sql[] = $type . ' ( ' . $u . ' )';
+ }
+ }
+
+ return ' ' . implode(' ', $sql);
+ }
+
+ /**
+ * index分析,可在操作链中指定需要强制使用的索引
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $index
+ * @return string
+ */
+ protected function parseForce(Query $query, $index): string
+ {
+ if (empty($index)) {
+ return '';
+ }
+
+ if (is_array($index)) {
+ $index = join(',', $index);
+ }
+
+ return sprintf(" FORCE INDEX ( %s ) ", $index);
+ }
+
+ /**
+ * 设置锁机制
+ * @access protected
+ * @param Query $query 查询对象
+ * @param bool|string $lock
+ * @return string
+ */
+ protected function parseLock(Query $query, $lock = false): string
+ {
+ if (is_bool($lock)) {
+ return $lock ? ' FOR UPDATE ' : '';
+ }
+
+ if (is_string($lock) && !empty($lock)) {
+ return ' ' . trim($lock) . ' ';
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * 生成查询SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @param bool $one 是否仅获取一个记录
+ * @return string
+ */
+ public function select(Query $query, bool $one = false): string
+ {
+ $options = $query->getOptions();
+
+ return str_replace(
+ ['%TABLE%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'],
+ [
+ $this->parseTable($query, $options['table']),
+ $this->parseDistinct($query, $options['distinct']),
+ $this->parseExtra($query, $options['extra']),
+ $this->parseField($query, $options['field'] ?? '*'),
+ $this->parseJoin($query, $options['join']),
+ $this->parseWhere($query, $options['where']),
+ $this->parseGroup($query, $options['group']),
+ $this->parseHaving($query, $options['having']),
+ $this->parseOrder($query, $options['order']),
+ $this->parseLimit($query, $one ? '1' : $options['limit']),
+ $this->parseUnion($query, $options['union']),
+ $this->parseLock($query, $options['lock']),
+ $this->parseComment($query, $options['comment']),
+ $this->parseForce($query, $options['force']),
+ ],
+ $this->selectSql);
+ }
+
+ /**
+ * 生成Insert SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @return string
+ */
+ public function insert(Query $query): string
+ {
+ $options = $query->getOptions();
+
+ // 分析并处理数据
+ $data = $this->parseData($query, $options['data']);
+ if (empty($data)) {
+ return '';
+ }
+
+ $fields = array_keys($data);
+ $values = array_values($data);
+
+ return str_replace(
+ ['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'],
+ [
+ !empty($options['replace']) ? 'REPLACE' : 'INSERT',
+ $this->parseTable($query, $options['table']),
+ $this->parseExtra($query, $options['extra']),
+ implode(' , ', $fields),
+ implode(' , ', $values),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->insertSql);
+ }
+
+ /**
+ * 生成insertall SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @param array $dataSet 数据集
+ * @return string
+ */
+ public function insertAll(Query $query, array $dataSet): string
+ {
+ $options = $query->getOptions();
+
+ // 获取绑定信息
+ $bind = $query->getFieldsBindType();
+
+ // 获取合法的字段
+ if (empty($options['field']) || '*' == $options['field']) {
+ $allowFields = array_keys($bind);
+ } else {
+ $allowFields = $options['field'];
+ }
+
+ $fields = [];
+ $values = [];
+
+ foreach ($dataSet as $k => $data) {
+ $data = $this->parseData($query, $data, $allowFields, $bind);
+
+ $values[] = 'SELECT ' . implode(',', array_values($data));
+
+ if (!isset($insertFields)) {
+ $insertFields = array_keys($data);
+ }
+ }
+
+ foreach ($insertFields as $field) {
+ $fields[] = $this->parseKey($query, $field);
+ }
+
+ return str_replace(
+ ['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'],
+ [
+ !empty($options['replace']) ? 'REPLACE' : 'INSERT',
+ $this->parseTable($query, $options['table']),
+ $this->parseExtra($query, $options['extra']),
+ implode(' , ', $fields),
+ implode(' UNION ALL ', $values),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->insertAllSql);
+ }
+
+ /**
+ * 生成slect insert SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @param array $fields 数据
+ * @param string $table 数据表
+ * @return string
+ */
+ public function selectInsert(Query $query, array $fields, string $table): string
+ {
+ foreach ($fields as &$field) {
+ $field = $this->parseKey($query, $field, true);
+ }
+
+ return 'INSERT INTO ' . $this->parseTable($query, $table) . ' (' . implode(',', $fields) . ') ' . $this->select($query);
+ }
+
+ /**
+ * 生成update SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @return string
+ */
+ public function update(Query $query): string
+ {
+ $options = $query->getOptions();
+
+ $data = $this->parseData($query, $options['data']);
+
+ if (empty($data)) {
+ return '';
+ }
+
+ $set = [];
+ foreach ($data as $key => $val) {
+ $set[] = $key . ' = ' . $val;
+ }
+
+ return str_replace(
+ ['%TABLE%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
+ [
+ $this->parseTable($query, $options['table']),
+ $this->parseExtra($query, $options['extra']),
+ implode(' , ', $set),
+ $this->parseJoin($query, $options['join']),
+ $this->parseWhere($query, $options['where']),
+ $this->parseOrder($query, $options['order']),
+ $this->parseLimit($query, $options['limit']),
+ $this->parseLock($query, $options['lock']),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->updateSql);
+ }
+
+ /**
+ * 生成delete SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @return string
+ */
+ public function delete(Query $query): string
+ {
+ $options = $query->getOptions();
+
+ return str_replace(
+ ['%TABLE%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
+ [
+ $this->parseTable($query, $options['table']),
+ $this->parseExtra($query, $options['extra']),
+ !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '',
+ $this->parseJoin($query, $options['join']),
+ $this->parseWhere($query, $options['where']),
+ $this->parseOrder($query, $options['order']),
+ $this->parseLimit($query, $options['limit']),
+ $this->parseLock($query, $options['lock']),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->deleteSql);
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/CacheItem.php b/vendor/topthink/think-orm/src/db/CacheItem.php
new file mode 100644
index 000000000..839f38409
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/CacheItem.php
@@ -0,0 +1,209 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use DateInterval;
+use DateTime;
+use DateTimeInterface;
+use think\db\exception\InvalidArgumentException;
+
+/**
+ * CacheItem实现类
+ */
+class CacheItem
+{
+ /**
+ * 缓存Key
+ * @var string
+ */
+ protected $key;
+
+ /**
+ * 缓存内容
+ * @var mixed
+ */
+ protected $value;
+
+ /**
+ * 过期时间
+ * @var int|DateTimeInterface
+ */
+ protected $expire;
+
+ /**
+ * 缓存tag
+ * @var string
+ */
+ protected $tag;
+
+ /**
+ * 缓存是否命中
+ * @var bool
+ */
+ protected $isHit = false;
+
+ public function __construct(string $key = null)
+ {
+ $this->key = $key;
+ }
+
+ /**
+ * 为此缓存项设置「键」
+ * @access public
+ * @param string $key
+ * @return $this
+ */
+ public function setKey(string $key)
+ {
+ $this->key = $key;
+ return $this;
+ }
+
+ /**
+ * 返回当前缓存项的「键」
+ * @access public
+ * @return string
+ */
+ public function getKey()
+ {
+ return $this->key;
+ }
+
+ /**
+ * 返回当前缓存项的有效期
+ * @access public
+ * @return DateTimeInterface|int|null
+ */
+ public function getExpire()
+ {
+ if ($this->expire instanceof DateTimeInterface) {
+ return $this->expire;
+ }
+
+ return $this->expire ? $this->expire - time() : null;
+ }
+
+ /**
+ * 获取缓存Tag
+ * @access public
+ * @return string|array
+ */
+ public function getTag()
+ {
+ return $this->tag;
+ }
+
+ /**
+ * 凭借此缓存项的「键」从缓存系统里面取出缓存项
+ * @access public
+ * @return mixed
+ */
+ public function get()
+ {
+ return $this->value;
+ }
+
+ /**
+ * 确认缓存项的检查是否命中
+ * @access public
+ * @return bool
+ */
+ public function isHit(): bool
+ {
+ return $this->isHit;
+ }
+
+ /**
+ * 为此缓存项设置「值」
+ * @access public
+ * @param mixed $value
+ * @return $this
+ */
+ public function set($value)
+ {
+ $this->value = $value;
+ $this->isHit = true;
+ return $this;
+ }
+
+ /**
+ * 为此缓存项设置所属标签
+ * @access public
+ * @param string|array $tag
+ * @return $this
+ */
+ public function tag($tag = null)
+ {
+ $this->tag = $tag;
+ return $this;
+ }
+
+ /**
+ * 设置缓存项的有效期
+ * @access public
+ * @param mixed $expire
+ * @return $this
+ */
+ public function expire($expire)
+ {
+ if (is_null($expire)) {
+ $this->expire = null;
+ } elseif (is_numeric($expire) || $expire instanceof DateInterval) {
+ $this->expiresAfter($expire);
+ } elseif ($expire instanceof DateTimeInterface) {
+ $this->expire = $expire;
+ } else {
+ throw new InvalidArgumentException('not support datetime');
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置缓存项的准确过期时间点
+ * @access public
+ * @param DateTimeInterface $expiration
+ * @return $this
+ */
+ public function expiresAt($expiration)
+ {
+ if ($expiration instanceof DateTimeInterface) {
+ $this->expire = $expiration;
+ } else {
+ throw new InvalidArgumentException('not support datetime');
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置缓存项的过期时间
+ * @access public
+ * @param int|DateInterval $timeInterval
+ * @return $this
+ * @throws InvalidArgumentException
+ */
+ public function expiresAfter($timeInterval)
+ {
+ if ($timeInterval instanceof DateInterval) {
+ $this->expire = (int) DateTime::createFromFormat('U', (string) time())->add($timeInterval)->format('U');
+ } elseif (is_numeric($timeInterval)) {
+ $this->expire = $timeInterval + time();
+ } else {
+ throw new InvalidArgumentException('not support datetime');
+ }
+
+ return $this;
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/Connection.php b/vendor/topthink/think-orm/src/db/Connection.php
new file mode 100644
index 000000000..aa86ba8e5
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/Connection.php
@@ -0,0 +1,347 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use Psr\SimpleCache\CacheInterface;
+use think\DbManager;
+
+/**
+ * 数据库连接基础类
+ */
+abstract class Connection implements ConnectionInterface
+{
+
+ /**
+ * 当前SQL指令
+ * @var string
+ */
+ protected $queryStr = '';
+
+ /**
+ * 返回或者影响记录数
+ * @var int
+ */
+ protected $numRows = 0;
+
+ /**
+ * 事务指令数
+ * @var int
+ */
+ protected $transTimes = 0;
+
+ /**
+ * 错误信息
+ * @var string
+ */
+ protected $error = '';
+
+ /**
+ * 数据库连接ID 支持多个连接
+ * @var array
+ */
+ protected $links = [];
+
+ /**
+ * 当前连接ID
+ * @var object
+ */
+ protected $linkID;
+
+ /**
+ * 当前读连接ID
+ * @var object
+ */
+ protected $linkRead;
+
+ /**
+ * 当前写连接ID
+ * @var object
+ */
+ protected $linkWrite;
+
+ /**
+ * 数据表信息
+ * @var array
+ */
+ protected $info = [];
+
+ /**
+ * 查询开始时间
+ * @var float
+ */
+ protected $queryStartTime;
+
+ /**
+ * Builder对象
+ * @var Builder
+ */
+ protected $builder;
+
+ /**
+ * Db对象
+ * @var DbManager
+ */
+ protected $db;
+
+ /**
+ * 是否读取主库
+ * @var bool
+ */
+ protected $readMaster = false;
+
+ /**
+ * 数据库连接参数配置
+ * @var array
+ */
+ protected $config = [];
+
+ /**
+ * 缓存对象
+ * @var CacheInterface
+ */
+ protected $cache;
+
+ /**
+ * 架构函数 读取数据库配置信息
+ * @access public
+ * @param array $config 数据库配置数组
+ */
+ public function __construct(array $config = [])
+ {
+ if (!empty($config)) {
+ $this->config = array_merge($this->config, $config);
+ }
+
+ // 创建Builder对象
+ $class = $this->getBuilderClass();
+
+ $this->builder = new $class($this);
+ }
+
+ /**
+ * 获取当前的builder实例对象
+ * @access public
+ * @return Builder
+ */
+ public function getBuilder()
+ {
+ return $this->builder;
+ }
+
+ /**
+ * 创建查询对象
+ */
+ public function newQuery()
+ {
+ $class = $this->getQueryClass();
+
+ /** @var BaseQuery $query */
+ $query = new $class($this);
+
+ $timeRule = $this->db->getConfig('time_query_rule');
+ if (!empty($timeRule)) {
+ $query->timeRule($timeRule);
+ }
+
+ return $query;
+ }
+
+ /**
+ * 指定表名开始查询
+ * @param $table
+ * @return BaseQuery
+ */
+ public function table($table)
+ {
+ return $this->newQuery()->table($table);
+ }
+
+ /**
+ * 指定表名开始查询(不带前缀)
+ * @param $name
+ * @return BaseQuery
+ */
+ public function name($name)
+ {
+ return $this->newQuery()->name($name);
+ }
+
+ /**
+ * 设置当前的数据库Db对象
+ * @access public
+ * @param DbManager $db
+ * @return void
+ */
+ public function setDb(DbManager $db)
+ {
+ $this->db = $db;
+ }
+
+ /**
+ * 设置当前的缓存对象
+ * @access public
+ * @param CacheInterface $cache
+ * @return void
+ */
+ public function setCache(CacheInterface $cache)
+ {
+ $this->cache = $cache;
+ }
+
+ /**
+ * 获取当前的缓存对象
+ * @access public
+ * @return CacheInterface|null
+ */
+ public function getCache()
+ {
+ return $this->cache;
+ }
+
+ /**
+ * 获取数据库的配置参数
+ * @access public
+ * @param string $config 配置名称
+ * @return mixed
+ */
+ public function getConfig(string $config = '')
+ {
+ if ('' === $config) {
+ return $this->config;
+ }
+
+ return $this->config[$config] ?? null;
+ }
+
+ /**
+ * 数据库SQL监控
+ * @access protected
+ * @param string $sql 执行的SQL语句 留空自动获取
+ * @param bool $master 主从标记
+ * @return void
+ */
+ protected function trigger(string $sql = '', bool $master = false): void
+ {
+ $listen = $this->db->getListen();
+ if (empty($listen)) {
+ $listen[] = function ($sql, $time, $master) {
+ if (0 === strpos($sql, 'CONNECT:')) {
+ $this->db->log($sql);
+ return;
+ }
+
+ // 记录SQL
+ if (is_bool($master)) {
+ // 分布式记录当前操作的主从
+ $master = $master ? 'master|' : 'slave|';
+ } else {
+ $master = '';
+ }
+
+ $this->db->log($sql . ' [ ' . $master . 'RunTime:' . $time . 's ]');
+ };
+ }
+
+ $runtime = number_format((microtime(true) - $this->queryStartTime), 6);
+ $sql = $sql ?: $this->getLastsql();
+
+ if (empty($this->config['deploy'])) {
+ $master = null;
+ }
+
+ foreach ($listen as $callback) {
+ if (is_callable($callback)) {
+ $callback($sql, $runtime, $master);
+ }
+ }
+ }
+
+ /**
+ * 缓存数据
+ * @access protected
+ * @param CacheItem $cacheItem 缓存Item
+ */
+ protected function cacheData(CacheItem $cacheItem)
+ {
+ if ($cacheItem->getTag() && method_exists($this->cache, 'tag')) {
+ $this->cache->tag($cacheItem->getTag())->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire());
+ } else {
+ $this->cache->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire());
+ }
+ }
+
+ /**
+ * 分析缓存Key
+ * @access protected
+ * @param BaseQuery $query 查询对象
+ * @param string $method 查询方法
+ * @return string
+ */
+ protected function getCacheKey(BaseQuery $query, string $method = ''): string
+ {
+ if (!empty($query->getOptions('key')) && empty($method)) {
+ $key = 'think_' . $this->getConfig('database') . '.' . $query->getTable() . '|' . $query->getOptions('key');
+ } else {
+ $key = $query->getQueryGuid();
+ }
+
+ return $key;
+ }
+
+ /**
+ * 分析缓存
+ * @access protected
+ * @param BaseQuery $query 查询对象
+ * @param array $cache 缓存信息
+ * @param string $method 查询方法
+ * @return CacheItem
+ */
+ protected function parseCache(BaseQuery $query, array $cache, string $method = ''): CacheItem
+ {
+ [$key, $expire, $tag] = $cache;
+
+ if ($key instanceof CacheItem) {
+ $cacheItem = $key;
+ } else {
+ if (true === $key) {
+ $key = $this->getCacheKey($query, $method);
+ }
+
+ $cacheItem = new CacheItem($key);
+ $cacheItem->expire($expire);
+ $cacheItem->tag($tag);
+ }
+
+ return $cacheItem;
+ }
+
+ /**
+ * 获取返回或者影响的记录数
+ * @access public
+ * @return integer
+ */
+ public function getNumRows(): int
+ {
+ return $this->numRows;
+ }
+
+ /**
+ * 析构方法
+ * @access public
+ */
+ public function __destruct()
+ {
+ // 关闭连接
+ $this->close();
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/ConnectionInterface.php b/vendor/topthink/think-orm/src/db/ConnectionInterface.php
new file mode 100644
index 000000000..18fe1316c
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/ConnectionInterface.php
@@ -0,0 +1,190 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use Psr\SimpleCache\CacheInterface;
+use think\DbManager;
+
+/**
+ * Connection interface
+ */
+interface ConnectionInterface
+{
+ /**
+ * 获取当前连接器类对应的Query类
+ * @access public
+ * @return string
+ */
+ public function getQueryClass(): string;
+
+ /**
+ * 指定表名开始查询
+ * @param $table
+ * @return BaseQuery
+ */
+ public function table($table);
+
+ /**
+ * 指定表名开始查询(不带前缀)
+ * @param $name
+ * @return BaseQuery
+ */
+ public function name($name);
+
+ /**
+ * 连接数据库方法
+ * @access public
+ * @param array $config 接参数
+ * @param integer $linkNum 连接序号
+ * @return mixed
+ */
+ public function connect(array $config = [], $linkNum = 0);
+
+ /**
+ * 设置当前的数据库Db对象
+ * @access public
+ * @param DbManager $db
+ * @return void
+ */
+ public function setDb(DbManager $db);
+
+ /**
+ * 设置当前的缓存对象
+ * @access public
+ * @param CacheInterface $cache
+ * @return void
+ */
+ public function setCache(CacheInterface $cache);
+
+ /**
+ * 获取数据库的配置参数
+ * @access public
+ * @param string $config 配置名称
+ * @return mixed
+ */
+ public function getConfig(string $config = '');
+
+ /**
+ * 关闭数据库(或者重新连接)
+ * @access public
+ * @return $this
+ */
+ public function close();
+
+ /**
+ * 查找单条记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return array
+ */
+ public function find(BaseQuery $query): array;
+
+ /**
+ * 查找记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return array
+ */
+ public function select(BaseQuery $query): array;
+
+ /**
+ * 插入记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param boolean $getLastInsID 返回自增主键
+ * @return mixed
+ */
+ public function insert(BaseQuery $query, bool $getLastInsID = false);
+
+ /**
+ * 批量插入记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param mixed $dataSet 数据集
+ * @return integer
+ */
+ public function insertAll(BaseQuery $query, array $dataSet = []): int;
+
+ /**
+ * 更新记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return integer
+ */
+ public function update(BaseQuery $query): int;
+
+ /**
+ * 删除记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return int
+ */
+ public function delete(BaseQuery $query): int;
+
+ /**
+ * 得到某个字段的值
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $field 字段名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function value(BaseQuery $query, string $field, $default = null);
+
+ /**
+ * 得到某个列的数组
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string|array $column 字段名 多个字段用逗号分隔
+ * @param string $key 索引
+ * @return array
+ */
+ public function column(BaseQuery $query, $column, string $key = ''): array;
+
+ /**
+ * 执行数据库事务
+ * @access public
+ * @param callable $callback 数据操作方法回调
+ * @return mixed
+ */
+ public function transaction(callable $callback);
+
+ /**
+ * 启动事务
+ * @access public
+ * @return void
+ */
+ public function startTrans();
+
+ /**
+ * 用于非自动提交状态下面的查询提交
+ * @access public
+ * @return void
+ */
+ public function commit();
+
+ /**
+ * 事务回滚
+ * @access public
+ * @return void
+ */
+ public function rollback();
+
+ /**
+ * 获取最近一次查询的sql语句
+ * @access public
+ * @return string
+ */
+ public function getLastSql(): string;
+
+}
diff --git a/vendor/topthink/think-orm/src/db/Fetch.php b/vendor/topthink/think-orm/src/db/Fetch.php
new file mode 100644
index 000000000..16caed2b0
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/Fetch.php
@@ -0,0 +1,494 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use think\db\exception\DbException as Exception;
+use think\helper\Str;
+
+/**
+ * SQL获取类
+ */
+class Fetch
+{
+ /**
+ * 查询对象
+ * @var Query
+ */
+ protected $query;
+
+ /**
+ * Connection对象
+ * @var Connection
+ */
+ protected $connection;
+
+ /**
+ * Builder对象
+ * @var Builder
+ */
+ protected $builder;
+
+ /**
+ * 创建一个查询SQL获取对象
+ *
+ * @param Query $query 查询对象
+ */
+ public function __construct(Query $query)
+ {
+ $this->query = $query;
+ $this->connection = $query->getConnection();
+ $this->builder = $this->connection->getBuilder();
+ }
+
+ /**
+ * 聚合查询
+ * @access protected
+ * @param string $aggregate 聚合方法
+ * @param string $field 字段名
+ * @return string
+ */
+ protected function aggregate(string $aggregate, string $field): string
+ {
+ $this->query->parseOptions();
+
+ $field = $aggregate . '(' . $this->builder->parseKey($this->query, $field) . ') AS think_' . strtolower($aggregate);
+
+ return $this->value($field, 0, false);
+ }
+
+ /**
+ * 得到某个字段的值
+ * @access public
+ * @param string $field 字段名
+ * @param mixed $default 默认值
+ * @param bool $one
+ * @return string
+ */
+ public function value(string $field, $default = null, bool $one = true): string
+ {
+ $options = $this->query->parseOptions();
+
+ if (isset($options['field'])) {
+ $this->query->removeOption('field');
+ }
+
+ $this->query->setOption('field', (array) $field);
+
+ // 生成查询SQL
+ $sql = $this->builder->select($this->query, $one);
+
+ if (isset($options['field'])) {
+ $this->query->setOption('field', $options['field']);
+ } else {
+ $this->query->removeOption('field');
+ }
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 得到某个列的数组
+ * @access public
+ * @param string $field 字段名 多个字段用逗号分隔
+ * @param string $key 索引
+ * @return string
+ */
+ public function column(string $field, string $key = ''): string
+ {
+ $options = $this->query->parseOptions();
+
+ if (isset($options['field'])) {
+ $this->query->removeOption('field');
+ }
+
+ if ($key && '*' != $field) {
+ $field = $key . ',' . $field;
+ }
+
+ $field = array_map('trim', explode(',', $field));
+
+ $this->query->setOption('field', $field);
+
+ // 生成查询SQL
+ $sql = $this->builder->select($this->query);
+
+ if (isset($options['field'])) {
+ $this->query->setOption('field', $options['field']);
+ } else {
+ $this->query->removeOption('field');
+ }
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 插入记录
+ * @access public
+ * @param array $data 数据
+ * @return string
+ */
+ public function insert(array $data = []): string
+ {
+ $options = $this->query->parseOptions();
+
+ if (!empty($data)) {
+ $this->query->setOption('data', $data);
+ }
+
+ $sql = $this->builder->insert($this->query);
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 插入记录并获取自增ID
+ * @access public
+ * @param array $data 数据
+ * @return string
+ */
+ public function insertGetId(array $data = []): string
+ {
+ return $this->insert($data);
+ }
+
+ /**
+ * 保存数据 自动判断insert或者update
+ * @access public
+ * @param array $data 数据
+ * @param bool $forceInsert 是否强制insert
+ * @return string
+ */
+ public function save(array $data = [], bool $forceInsert = false): string
+ {
+ if ($forceInsert) {
+ return $this->insert($data);
+ }
+
+ $data = array_merge($this->query->getOptions('data') ?: [], $data);
+
+ $this->query->setOption('data', $data);
+
+ if ($this->query->getOptions('where')) {
+ $isUpdate = true;
+ } else {
+ $isUpdate = $this->query->parseUpdateData($data);
+ }
+
+ return $isUpdate ? $this->update() : $this->insert();
+ }
+
+ /**
+ * 批量插入记录
+ * @access public
+ * @param array $dataSet 数据集
+ * @param integer $limit 每次写入数据限制
+ * @return string
+ */
+ public function insertAll(array $dataSet = [], int $limit = null): string
+ {
+ $options = $this->query->parseOptions();
+
+ if (empty($dataSet)) {
+ $dataSet = $options['data'];
+ }
+
+ if (empty($limit) && !empty($options['limit'])) {
+ $limit = $options['limit'];
+ }
+
+ if ($limit) {
+ $array = array_chunk($dataSet, $limit, true);
+ $fetchSql = [];
+ foreach ($array as $item) {
+ $sql = $this->builder->insertAll($this->query, $item);
+ $bind = $this->query->getBind();
+
+ $fetchSql[] = $this->connection->getRealSql($sql, $bind);
+ }
+
+ return implode(';', $fetchSql);
+ }
+
+ $sql = $this->builder->insertAll($this->query, $dataSet);
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 通过Select方式插入记录
+ * @access public
+ * @param array $fields 要插入的数据表字段名
+ * @param string $table 要插入的数据表名
+ * @return string
+ */
+ public function selectInsert(array $fields, string $table): string
+ {
+ $this->query->parseOptions();
+
+ $sql = $this->builder->selectInsert($this->query, $fields, $table);
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 更新记录
+ * @access public
+ * @param mixed $data 数据
+ * @return string
+ */
+ public function update(array $data = []): string
+ {
+ $options = $this->query->parseOptions();
+
+ $data = !empty($data) ? $data : $options['data'];
+
+ $pk = $this->query->getPk();
+
+ if (empty($options['where'])) {
+ // 如果存在主键数据 则自动作为更新条件
+ if (is_string($pk) && isset($data[$pk])) {
+ $this->query->where($pk, '=', $data[$pk]);
+ unset($data[$pk]);
+ } elseif (is_array($pk)) {
+ // 增加复合主键支持
+ foreach ($pk as $field) {
+ if (isset($data[$field])) {
+ $this->query->where($field, '=', $data[$field]);
+ } else {
+ // 如果缺少复合主键数据则不执行
+ throw new Exception('miss complex primary data');
+ }
+ unset($data[$field]);
+ }
+ }
+
+ if (empty($this->query->getOptions('where'))) {
+ // 如果没有任何更新条件则不执行
+ throw new Exception('miss update condition');
+ }
+ }
+
+ // 更新数据
+ $this->query->setOption('data', $data);
+
+ // 生成UPDATE SQL语句
+ $sql = $this->builder->update($this->query);
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param mixed $data 表达式 true 表示强制删除
+ * @return string
+ */
+ public function delete($data = null): string
+ {
+ $options = $this->query->parseOptions();
+
+ if (!is_null($data) && true !== $data) {
+ // AR模式分析主键条件
+ $this->query->parsePkWhere($data);
+ }
+
+ if (!empty($options['soft_delete'])) {
+ // 软删除
+ [$field, $condition] = $options['soft_delete'];
+ if ($condition) {
+ $this->query->setOption('soft_delete', null);
+ $this->query->setOption('data', [$field => $condition]);
+ // 生成删除SQL语句
+ $sql = $this->builder->delete($this->query);
+ return $this->fetch($sql);
+ }
+ }
+
+ // 生成删除SQL语句
+ $sql = $this->builder->delete($this->query);
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 查找记录 返回SQL
+ * @access public
+ * @param mixed $data
+ * @return string
+ */
+ public function select($data = null): string
+ {
+ $this->query->parseOptions();
+
+ if (!is_null($data)) {
+ // 主键条件分析
+ $this->query->parsePkWhere($data);
+ }
+
+ // 生成查询SQL
+ $sql = $this->builder->select($this->query);
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 查找单条记录 返回SQL语句
+ * @access public
+ * @param mixed $data
+ * @return string
+ */
+ public function find($data = null): string
+ {
+ $this->query->parseOptions();
+
+ if (!is_null($data)) {
+ // AR模式分析主键条件
+ $this->query->parsePkWhere($data);
+ }
+
+ // 生成查询SQL
+ $sql = $this->builder->select($this->query, true);
+
+ // 获取实际执行的SQL语句
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 查找多条记录 如果不存在则抛出异常
+ * @access public
+ * @param mixed $data
+ * @return string
+ */
+ public function selectOrFail($data = null): string
+ {
+ return $this->select($data);
+ }
+
+ /**
+ * 查找单条记录 如果不存在则抛出异常
+ * @access public
+ * @param mixed $data
+ * @return string
+ */
+ public function findOrFail($data = null): string
+ {
+ return $this->find($data);
+ }
+
+ /**
+ * 查找单条记录 不存在返回空数据(或者空模型)
+ * @access public
+ * @param mixed $data 数据
+ * @return string
+ */
+ public function findOrEmpty($data = null)
+ {
+ return $this->find($data);
+ }
+
+ /**
+ * 获取实际的SQL语句
+ * @access public
+ * @param string $sql
+ * @return string
+ */
+ public function fetch(string $sql): string
+ {
+ $bind = $this->query->getBind();
+
+ return $this->connection->getRealSql($sql, $bind);
+ }
+
+ /**
+ * COUNT查询
+ * @access public
+ * @param string $field 字段名
+ * @return string
+ */
+ public function count(string $field = '*'): string
+ {
+ $options = $this->query->parseOptions();
+
+ if (!empty($options['group'])) {
+ // 支持GROUP
+ $bind = $this->query->getBind();
+ $subSql = $this->query->options($options)->field('count(' . $field . ') AS think_count')->bind($bind)->buildSql();
+
+ $query = $this->query->newQuery()->table([$subSql => '_group_count_']);
+
+ return $query->fetchsql()->aggregate('COUNT', '*');
+ } else {
+ return $this->aggregate('COUNT', $field);
+ }
+ }
+
+ /**
+ * SUM查询
+ * @access public
+ * @param string $field 字段名
+ * @return string
+ */
+ public function sum(string $field): string
+ {
+ return $this->aggregate('SUM', $field);
+ }
+
+ /**
+ * MIN查询
+ * @access public
+ * @param string $field 字段名
+ * @return string
+ */
+ public function min(string $field): string
+ {
+ return $this->aggregate('MIN', $field);
+ }
+
+ /**
+ * MAX查询
+ * @access public
+ * @param string $field 字段名
+ * @return string
+ */
+ public function max(string $field): string
+ {
+ return $this->aggregate('MAX', $field);
+ }
+
+ /**
+ * AVG查询
+ * @access public
+ * @param string $field 字段名
+ * @return string
+ */
+ public function avg(string $field): string
+ {
+ return $this->aggregate('AVG', $field);
+ }
+
+ public function __call($method, $args)
+ {
+ if (strtolower(substr($method, 0, 5)) == 'getby') {
+ // 根据某个字段获取记录
+ $field = Str::snake(substr($method, 5));
+ return $this->where($field, '=', $args[0])->find();
+ } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
+ // 根据某个字段获取记录的某个值
+ $name = Str::snake(substr($method, 10));
+ return $this->where($name, '=', $args[0])->value($args[1]);
+ }
+
+ $result = call_user_func_array([$this->query, $method], $args);
+ return $result === $this->query ? $this : $result;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/Mongo.php b/vendor/topthink/think-orm/src/db/Mongo.php
new file mode 100644
index 000000000..5e8a09a51
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/Mongo.php
@@ -0,0 +1,712 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+namespace think\db;
+
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Exception\AuthenticationException;
+use MongoDB\Driver\Exception\ConnectionException;
+use MongoDB\Driver\Exception\InvalidArgumentException;
+use MongoDB\Driver\Exception\RuntimeException;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\WriteConcern;
+use think\db\exception\DbException as Exception;
+use think\Paginator;
+
+class Mongo extends BaseQuery
+{
+ /**
+ * 当前数据库连接对象
+ * @var \think\db\connector\Mongo
+ */
+ protected $connection;
+
+ /**
+ * 执行指令 返回数据集
+ * @access public
+ * @param Command $command 指令
+ * @param string $dbName
+ * @param ReadPreference $readPreference readPreference
+ * @param string|array $typeMap 指定返回的typeMap
+ * @return mixed
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ */
+ public function command(Command $command, string $dbName = '', ReadPreference $readPreference = null, $typeMap = null)
+ {
+ return $this->connection->command($command, $dbName, $readPreference, $typeMap);
+ }
+
+ /**
+ * 执行command
+ * @access public
+ * @param string|array|object $command 指令
+ * @param mixed $extra 额外参数
+ * @param string $db 数据库名
+ * @return array
+ */
+ public function cmd($command, $extra = null, string $db = ''): array
+ {
+ $this->parseOptions();
+ return $this->connection->cmd($this, $command, $extra, $db);
+ }
+
+ /**
+ * 指定distinct查询
+ * @access public
+ * @param string $field 字段名
+ * @return array
+ */
+ public function getDistinct(string $field)
+ {
+ $result = $this->cmd('distinct', $field);
+ return $result[0]['values'];
+ }
+
+ /**
+ * 获取数据库的所有collection
+ * @access public
+ * @param string $db 数据库名称 留空为当前数据库
+ * @throws Exception
+ */
+ public function listCollections(string $db = '')
+ {
+ $cursor = $this->cmd('listCollections', null, $db);
+ $result = [];
+ foreach ($cursor as $collection) {
+ $result[] = $collection['name'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * COUNT查询
+ * @access public
+ * @param string $field 字段名
+ * @return integer
+ */
+ public function count(string $field = null): int
+ {
+ $result = $this->cmd('count');
+
+ return $result[0]['n'];
+ }
+
+ /**
+ * 聚合查询
+ * @access public
+ * @param string $aggregate 聚合指令
+ * @param string $field 字段名
+ * @param bool $force 强制转为数字类型
+ * @return mixed
+ */
+ public function aggregate(string $aggregate, $field, bool $force = false)
+ {
+ $result = $this->cmd('aggregate', [strtolower($aggregate), $field]);
+ $value = $result[0]['aggregate'] ?? 0;
+
+ if ($force) {
+ $value += 0;
+ }
+
+ return $value;
+ }
+
+ /**
+ * 多聚合操作
+ *
+ * @param array $aggregate 聚合指令, 可以聚合多个参数, 如 ['sum' => 'field1', 'avg' => 'field2']
+ * @param array $groupBy 类似mysql里面的group字段, 可以传入多个字段, 如 ['field_a', 'field_b', 'field_c']
+ * @return array 查询结果
+ */
+ public function multiAggregate(array $aggregate, array $groupBy): array
+ {
+ $result = $this->cmd('multiAggregate', [$aggregate, $groupBy]);
+
+ foreach ($result as &$row) {
+ if (isset($row['_id']) && !empty($row['_id'])) {
+ foreach ($row['_id'] as $k => $v) {
+ $row[$k] = $v;
+ }
+ unset($row['_id']);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 字段值增长
+ * @access public
+ * @param string $field 字段名
+ * @param float $step 增长值
+ * @return $this
+ */
+ public function inc(string $field, float $step = 1)
+ {
+ $this->options['data'][$field] = ['$inc', $step];
+
+ return $this;
+ }
+
+ /**
+ * 字段值减少
+ * @access public
+ * @param string $field 字段名
+ * @param float $step 减少值
+ * @return $this
+ */
+ public function dec(string $field, float $step = 1)
+ {
+ return $this->inc($field, -1 * $step);
+ }
+
+ /**
+ * 指定当前操作的Collection
+ * @access public
+ * @param string $table 表名
+ * @return $this
+ */
+ public function table($table)
+ {
+ $this->options['table'] = $table;
+
+ return $this;
+ }
+
+ /**
+ * table方法的别名
+ * @access public
+ * @param string $collection
+ * @return $this
+ */
+ public function collection(string $collection)
+ {
+ return $this->table($collection);
+ }
+
+ /**
+ * 设置typeMap
+ * @access public
+ * @param string|array $typeMap
+ * @return $this
+ */
+ public function typeMap($typeMap)
+ {
+ $this->options['typeMap'] = $typeMap;
+ return $this;
+ }
+
+ /**
+ * awaitData
+ * @access public
+ * @param bool $awaitData
+ * @return $this
+ */
+ public function awaitData(bool $awaitData)
+ {
+ $this->options['awaitData'] = $awaitData;
+ return $this;
+ }
+
+ /**
+ * batchSize
+ * @access public
+ * @param integer $batchSize
+ * @return $this
+ */
+ public function batchSize(int $batchSize)
+ {
+ $this->options['batchSize'] = $batchSize;
+ return $this;
+ }
+
+ /**
+ * exhaust
+ * @access public
+ * @param bool $exhaust
+ * @return $this
+ */
+ public function exhaust(bool $exhaust)
+ {
+ $this->options['exhaust'] = $exhaust;
+ return $this;
+ }
+
+ /**
+ * 设置modifiers
+ * @access public
+ * @param array $modifiers
+ * @return $this
+ */
+ public function modifiers(array $modifiers)
+ {
+ $this->options['modifiers'] = $modifiers;
+ return $this;
+ }
+
+ /**
+ * 设置noCursorTimeout
+ * @access public
+ * @param bool $noCursorTimeout
+ * @return $this
+ */
+ public function noCursorTimeout(bool $noCursorTimeout)
+ {
+ $this->options['noCursorTimeout'] = $noCursorTimeout;
+ return $this;
+ }
+
+ /**
+ * 设置oplogReplay
+ * @access public
+ * @param bool $oplogReplay
+ * @return $this
+ */
+ public function oplogReplay(bool $oplogReplay)
+ {
+ $this->options['oplogReplay'] = $oplogReplay;
+ return $this;
+ }
+
+ /**
+ * 设置partial
+ * @access public
+ * @param bool $partial
+ * @return $this
+ */
+ public function partial(bool $partial)
+ {
+ $this->options['partial'] = $partial;
+ return $this;
+ }
+
+ /**
+ * maxTimeMS
+ * @access public
+ * @param string $maxTimeMS
+ * @return $this
+ */
+ public function maxTimeMS(string $maxTimeMS)
+ {
+ $this->options['maxTimeMS'] = $maxTimeMS;
+ return $this;
+ }
+
+ /**
+ * collation
+ * @access public
+ * @param array $collation
+ * @return $this
+ */
+ public function collation(array $collation)
+ {
+ $this->options['collation'] = $collation;
+ return $this;
+ }
+
+ /**
+ * 设置是否REPLACE
+ * @access public
+ * @param bool $replace 是否使用REPLACE写入数据
+ * @return $this
+ */
+ public function replace(bool $replace = true)
+ {
+ return $this;
+ }
+
+ /**
+ * 设置返回字段
+ * @access public
+ * @param mixed $field 字段信息
+ * @return $this
+ */
+ public function field($field)
+ {
+ if (empty($field) || '*' == $field) {
+ return $this;
+ }
+
+ if (is_string($field)) {
+ $field = array_map('trim', explode(',', $field));
+ }
+
+ $projection = [];
+ foreach ($field as $key => $val) {
+ if (is_numeric($key)) {
+ $projection[$val] = 1;
+ } else {
+ $projection[$key] = $val;
+ }
+ }
+
+ $this->options['projection'] = $projection;
+
+ return $this;
+ }
+
+ /**
+ * 指定要排除的查询字段
+ * @access public
+ * @param array|string $field 要排除的字段
+ * @return $this
+ */
+ public function withoutField($field)
+ {
+ if (empty($field) || '*' == $field) {
+ return $this;
+ }
+
+ if (is_string($field)) {
+ $field = array_map('trim', explode(',', $field));
+ }
+
+ $projection = [];
+ foreach ($field as $key => $val) {
+ if (is_numeric($key)) {
+ $projection[$val] = 0;
+ } else {
+ $projection[$key] = $val;
+ }
+ }
+
+ $this->options['projection'] = $projection;
+ return $this;
+ }
+
+ /**
+ * 设置skip
+ * @access public
+ * @param integer $skip
+ * @return $this
+ */
+ public function skip(int $skip)
+ {
+ $this->options['skip'] = $skip;
+ return $this;
+ }
+
+ /**
+ * 设置slaveOk
+ * @access public
+ * @param bool $slaveOk
+ * @return $this
+ */
+ public function slaveOk(bool $slaveOk)
+ {
+ $this->options['slaveOk'] = $slaveOk;
+ return $this;
+ }
+
+ /**
+ * 指定查询数量
+ * @access public
+ * @param int $offset 起始位置
+ * @param int $length 查询数量
+ * @return $this
+ */
+ public function limit(int $offset, int $length = null)
+ {
+ if (is_null($length)) {
+ $length = $offset;
+ $offset = 0;
+ }
+
+ $this->options['skip'] = $offset;
+ $this->options['limit'] = $length;
+
+ return $this;
+ }
+
+ /**
+ * 设置sort
+ * @access public
+ * @param array|string $field
+ * @param string $order
+ * @return $this
+ */
+ public function order($field, string $order = '')
+ {
+ if (is_array($field)) {
+ $this->options['sort'] = $field;
+ } else {
+ $this->options['sort'][$field] = 'asc' == strtolower($order) ? 1 : -1;
+ }
+ return $this;
+ }
+
+ /**
+ * 设置tailable
+ * @access public
+ * @param bool $tailable
+ * @return $this
+ */
+ public function tailable(bool $tailable)
+ {
+ $this->options['tailable'] = $tailable;
+ return $this;
+ }
+
+ /**
+ * 设置writeConcern对象
+ * @access public
+ * @param WriteConcern $writeConcern
+ * @return $this
+ */
+ public function writeConcern(WriteConcern $writeConcern)
+ {
+ $this->options['writeConcern'] = $writeConcern;
+ return $this;
+ }
+
+ /**
+ * 获取当前数据表的主键
+ * @access public
+ * @return string|array
+ */
+ public function getPk()
+ {
+ return $this->pk ?: $this->connection->getConfig('pk');
+ }
+
+ /**
+ * 执行查询但只返回Cursor对象
+ * @access public
+ * @return Cursor
+ */
+ public function getCursor(): Cursor
+ {
+ $this->parseOptions();
+
+ return $this->connection->getCursor($this);
+ }
+
+ /**
+ * 获取当前的查询标识
+ * @access public
+ * @param mixed $data 要序列化的数据
+ * @return string
+ */
+ public function getQueryGuid($data = null): string
+ {
+ return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true)));
+ }
+
+ /**
+ * 分页查询
+ * @access public
+ * @param int|array $listRows 每页数量 数组表示配置参数
+ * @param int|bool $simple 是否简洁模式或者总记录数
+ * @return Paginator
+ * @throws Exception
+ */
+ public function paginate($listRows = null, $simple = false): Paginator
+ {
+ if (is_int($simple)) {
+ $total = $simple;
+ $simple = false;
+ }
+
+ $defaultConfig = [
+ 'query' => [], //url额外参数
+ 'fragment' => '', //url锚点
+ 'var_page' => 'page', //分页变量
+ 'list_rows' => 15, //每页数量
+ ];
+
+ if (is_array($listRows)) {
+ $config = array_merge($defaultConfig, $listRows);
+ $listRows = intval($config['list_rows']);
+ } else {
+ $config = $defaultConfig;
+ $listRows = intval($listRows ?: $config['list_rows']);
+ }
+
+ $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']);
+
+ $page = $page < 1 ? 1 : $page;
+
+ $config['path'] = $config['path'] ?? Paginator::getCurrentPath();
+
+ if (!isset($total) && !$simple) {
+ $options = $this->getOptions();
+
+ unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
+
+ $total = $this->count();
+ $results = $this->options($options)->page($page, $listRows)->select();
+ } elseif ($simple) {
+ $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
+ $total = null;
+ } else {
+ $results = $this->page($page, $listRows)->select();
+ }
+
+ $this->removeOption('limit');
+ $this->removeOption('page');
+
+ return Paginator::make($results, $listRows, $page, $total, $simple, $config);
+ }
+
+ /**
+ * 分批数据返回处理
+ * @access public
+ * @param integer $count 每次处理的数据数量
+ * @param callable $callback 处理回调方法
+ * @param string|array $column 分批处理的字段名
+ * @param string $order 字段排序
+ * @return bool
+ * @throws Exception
+ */
+ public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
+ {
+ $options = $this->getOptions();
+ $column = $column ?: $this->getPk();
+
+ if (isset($options['order'])) {
+ unset($options['order']);
+ }
+
+ if (is_array($column)) {
+ $times = 1;
+ $query = $this->options($options)->page($times, $count);
+ } else {
+ $query = $this->options($options)->limit($count);
+
+ if (strpos($column, '.')) {
+ [$alias, $key] = explode('.', $column);
+ } else {
+ $key = $column;
+ }
+ }
+
+ $resultSet = $query->order($column, $order)->select();
+
+ while (count($resultSet) > 0) {
+ if (false === call_user_func($callback, $resultSet)) {
+ return false;
+ }
+
+ if (isset($times)) {
+ $times++;
+ $query = $this->options($options)->page($times, $count);
+ } else {
+ $end = $resultSet->pop();
+ $lastId = is_array($end) ? $end[$key] : $end->getData($key);
+
+ $query = $this->options($options)
+ ->limit($count)
+ ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
+ }
+
+ $resultSet = $query->order($column, $order)->select();
+ }
+
+ return true;
+ }
+
+ /**
+ * 分析表达式(可用于查询或者写入操作)
+ * @access public
+ * @return array
+ */
+ public function parseOptions(): array
+ {
+ $options = $this->options;
+
+ // 获取数据表
+ if (empty($options['table'])) {
+ $options['table'] = $this->getTable();
+ }
+
+ foreach (['where', 'data'] as $name) {
+ if (!isset($options[$name])) {
+ $options[$name] = [];
+ }
+ }
+
+ $modifiers = empty($options['modifiers']) ? [] : $options['modifiers'];
+ if (isset($options['comment'])) {
+ $modifiers['$comment'] = $options['comment'];
+ }
+
+ if (isset($options['maxTimeMS'])) {
+ $modifiers['$maxTimeMS'] = $options['maxTimeMS'];
+ }
+
+ if (!empty($modifiers)) {
+ $options['modifiers'] = $modifiers;
+ }
+
+ if (!isset($options['projection'])) {
+ $options['projection'] = [];
+ }
+
+ if (!isset($options['typeMap'])) {
+ $options['typeMap'] = $this->getConfig('type_map');
+ }
+
+ if (!isset($options['limit'])) {
+ $options['limit'] = 0;
+ }
+
+ foreach (['master', 'fetch_sql', 'fetch_cursor'] as $name) {
+ if (!isset($options[$name])) {
+ $options[$name] = false;
+ }
+ }
+
+ if (isset($options['page'])) {
+ // 根据页数计算limit
+ [$page, $listRows] = $options['page'];
+
+ $page = $page > 0 ? $page : 1;
+ $listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
+ $offset = $listRows * ($page - 1);
+ $options['skip'] = intval($offset);
+ $options['limit'] = intval($listRows);
+ }
+
+ $this->options = $options;
+
+ return $options;
+ }
+
+ /**
+ * 获取字段类型信息
+ * @access public
+ * @return array
+ */
+ public function getFieldsType(): array
+ {
+ if (!empty($this->options['field_type'])) {
+ return $this->options['field_type'];
+ }
+
+ return [];
+ }
+
+ /**
+ * 获取字段类型信息
+ * @access public
+ * @param string $field 字段名
+ * @return string|null
+ */
+ public function getFieldType(string $field)
+ {
+ $fieldType = $this->getFieldsType();
+
+ return $fieldType[$field] ?? null;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/PDOConnection.php b/vendor/topthink/think-orm/src/db/PDOConnection.php
new file mode 100644
index 000000000..0a51c0c4f
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/PDOConnection.php
@@ -0,0 +1,1792 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use Closure;
+use PDO;
+use PDOStatement;
+use think\db\exception\BindParamException;
+use think\db\exception\DbException;
+use think\db\exception\PDOException;
+use think\Model;
+
+/**
+ * 数据库连接基础类
+ * @property PDO[] $links
+ * @property PDO $linkID
+ * @property PDO $linkRead
+ * @property PDO $linkWrite
+ */
+abstract class PDOConnection extends Connection
+{
+ const PARAM_FLOAT = 21;
+
+ /**
+ * 数据库连接参数配置
+ * @var array
+ */
+ protected $config = [
+ // 数据库类型
+ 'type' => '',
+ // 服务器地址
+ 'hostname' => '',
+ // 数据库名
+ 'database' => '',
+ // 用户名
+ 'username' => '',
+ // 密码
+ 'password' => '',
+ // 端口
+ 'hostport' => '',
+ // 连接dsn
+ 'dsn' => '',
+ // 数据库连接参数
+ 'params' => [],
+ // 数据库编码默认采用utf8
+ 'charset' => 'utf8',
+ // 数据库表前缀
+ 'prefix' => '',
+ // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
+ 'deploy' => 0,
+ // 数据库读写是否分离 主从式有效
+ 'rw_separate' => false,
+ // 读写分离后 主服务器数量
+ 'master_num' => 1,
+ // 指定从服务器序号
+ 'slave_no' => '',
+ // 模型写入后自动读取主服务器
+ 'read_master' => false,
+ // 是否严格检查字段是否存在
+ 'fields_strict' => true,
+ // 开启字段缓存
+ 'fields_cache' => false,
+ // 监听SQL
+ 'trigger_sql' => true,
+ // Builder类
+ 'builder' => '',
+ // Query类
+ 'query' => '',
+ // 是否需要断线重连
+ 'break_reconnect' => false,
+ // 断线标识字符串
+ 'break_match_str' => [],
+ ];
+
+ /**
+ * PDO操作实例
+ * @var PDOStatement
+ */
+ protected $PDOStatement;
+
+ /**
+ * 当前SQL指令
+ * @var string
+ */
+ protected $queryStr = '';
+
+ /**
+ * 事务指令数
+ * @var int
+ */
+ protected $transTimes = 0;
+
+ /**
+ * 重连次数
+ * @var int
+ */
+ protected $reConnectTimes = 0;
+
+ /**
+ * 查询结果类型
+ * @var int
+ */
+ protected $fetchType = PDO::FETCH_ASSOC;
+
+ /**
+ * 字段属性大小写
+ * @var int
+ */
+ protected $attrCase = PDO::CASE_LOWER;
+
+ /**
+ * 数据表信息
+ * @var array
+ */
+ protected $info = [];
+
+ /**
+ * 查询开始时间
+ * @var float
+ */
+ protected $queryStartTime;
+
+ /**
+ * PDO连接参数
+ * @var array
+ */
+ protected $params = [
+ PDO::ATTR_CASE => PDO::CASE_NATURAL,
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
+ PDO::ATTR_STRINGIFY_FETCHES => false,
+ PDO::ATTR_EMULATE_PREPARES => false,
+ ];
+
+ /**
+ * 参数绑定类型映射
+ * @var array
+ */
+ protected $bindType = [
+ 'string' => PDO::PARAM_STR,
+ 'str' => PDO::PARAM_STR,
+ 'integer' => PDO::PARAM_INT,
+ 'int' => PDO::PARAM_INT,
+ 'boolean' => PDO::PARAM_BOOL,
+ 'bool' => PDO::PARAM_BOOL,
+ 'float' => self::PARAM_FLOAT,
+ 'datetime' => PDO::PARAM_STR,
+ 'timestamp' => PDO::PARAM_STR,
+ ];
+
+ /**
+ * 服务器断线标识字符
+ * @var array
+ */
+ protected $breakMatchStr = [
+ 'server has gone away',
+ 'no connection to the server',
+ 'Lost connection',
+ 'is dead or not enabled',
+ 'Error while sending',
+ 'decryption failed or bad record mac',
+ 'server closed the connection unexpectedly',
+ 'SSL connection has been closed unexpectedly',
+ 'Error writing data to the connection',
+ 'Resource deadlock avoided',
+ 'failed with errno',
+ 'child connection forced to terminate due to client_idle_limit',
+ 'query_wait_timeout',
+ 'reset by peer',
+ 'Physical connection is not usable',
+ 'TCP Provider: Error code 0x68',
+ 'ORA-03114',
+ 'Packets out of order. Expected',
+ 'Adaptive Server connection failed',
+ 'Communication link failure',
+ 'connection is no longer usable',
+ 'Login timeout expired',
+ 'SQLSTATE[HY000] [2002] Connection refused',
+ 'running with the --read-only option so it cannot execute this statement',
+ 'The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.',
+ 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again',
+ 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known',
+ 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected',
+ 'SQLSTATE[HY000] [2002] Connection timed out',
+ 'SSL: Connection timed out',
+ 'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.',
+ ];
+
+ /**
+ * 绑定参数
+ * @var array
+ */
+ protected $bind = [];
+
+ /**
+ * 获取当前连接器类对应的Query类
+ * @access public
+ * @return string
+ */
+ public function getQueryClass(): string
+ {
+ return $this->getConfig('query') ?: Query::class;
+ }
+
+ /**
+ * 获取当前连接器类对应的Builder类
+ * @access public
+ * @return string
+ */
+ public function getBuilderClass(): string
+ {
+ return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type'));
+ }
+
+ /**
+ * 解析pdo连接的dsn信息
+ * @access protected
+ * @param array $config 连接信息
+ * @return string
+ */
+ abstract protected function parseDsn(array $config): string;
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @param string $tableName 数据表名称
+ * @return array
+ */
+ abstract public function getFields(string $tableName): array;
+
+ /**
+ * 取得数据库的表信息
+ * @access public
+ * @param string $dbName 数据库名称
+ * @return array
+ */
+ abstract public function getTables(string $dbName = ''): array;
+
+ /**
+ * 对返数据表字段信息进行大小写转换出来
+ * @access public
+ * @param array $info 字段信息
+ * @return array
+ */
+ public function fieldCase(array $info): array
+ {
+ // 字段大小写转换
+ switch ($this->attrCase) {
+ case PDO::CASE_LOWER:
+ $info = array_change_key_case($info);
+ break;
+ case PDO::CASE_UPPER:
+ $info = array_change_key_case($info, CASE_UPPER);
+ break;
+ case PDO::CASE_NATURAL:
+ default:
+ // 不做转换
+ }
+
+ return $info;
+ }
+
+ /**
+ * 获取字段类型
+ * @access protected
+ * @param string $type 字段类型
+ * @return string
+ */
+ protected function getFieldType(string $type): string
+ {
+ if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) {
+ $result = 'string';
+ } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) {
+ $result = 'float';
+ } elseif (preg_match('/(int|serial|bit)/is', $type)) {
+ $result = 'int';
+ } elseif (preg_match('/bool/is', $type)) {
+ $result = 'bool';
+ } elseif (0 === strpos($type, 'timestamp')) {
+ $result = 'timestamp';
+ } elseif (0 === strpos($type, 'datetime')) {
+ $result = 'datetime';
+ } elseif (0 === strpos($type, 'date')) {
+ $result = 'date';
+ } else {
+ $result = 'string';
+ }
+
+ return $result;
+ }
+
+ /**
+ * 获取字段绑定类型
+ * @access public
+ * @param string $type 字段类型
+ * @return integer
+ */
+ public function getFieldBindType(string $type): int
+ {
+ if (in_array($type, ['integer', 'string', 'float', 'boolean', 'bool', 'int', 'str'])) {
+ $bind = $this->bindType[$type];
+ } elseif (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) {
+ $bind = PDO::PARAM_STR;
+ } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) {
+ $bind = self::PARAM_FLOAT;
+ } elseif (preg_match('/(int|serial|bit)/is', $type)) {
+ $bind = PDO::PARAM_INT;
+ } elseif (preg_match('/bool/is', $type)) {
+ $bind = PDO::PARAM_BOOL;
+ } else {
+ $bind = PDO::PARAM_STR;
+ }
+
+ return $bind;
+ }
+
+ /**
+ * 获取数据表信息缓存key
+ * @access protected
+ * @param string $schema 数据表名称
+ * @return string
+ */
+ protected function getSchemaCacheKey(string $schema): string
+ {
+ return $this->getConfig('hostname') . ':' . $this->getConfig('hostport') . '@' . $schema;
+ }
+
+ /**
+ * @param string $tableName 数据表名称
+ * @param bool $force 强制从数据库获取
+ * @return array
+ */
+ public function getSchemaInfo(string $tableName, $force = false)
+ {
+ if (!strpos($tableName, '.')) {
+ $schema = $this->getConfig('database') . '.' . $tableName;
+ } else {
+ $schema = $tableName;
+ }
+
+ if (!isset($this->info[$schema]) || $force) {
+ // 读取字段缓存
+ $cacheKey = $this->getSchemaCacheKey($schema);
+ $cacheField = $this->config['fields_cache'] && !empty($this->cache);
+
+ if ($cacheField && !$force) {
+ $info = $this->cache->get($cacheKey);
+ }
+
+ if (empty($info)) {
+ $info = $this->getTableFieldsInfo($tableName);
+ if ($cacheField) {
+ $this->cache->set($cacheKey, $info);
+ }
+ }
+
+ $pk = $info['_pk'] ?? null;
+ $autoinc = $info['_autoinc'] ?? null;
+ unset($info['_pk'], $info['_autoinc']);
+
+ $bind = [];
+ foreach ($info as $name => $val) {
+ $bind[$name] = $this->getFieldBindType($val);
+ }
+
+ $this->info[$schema] = [
+ 'fields' => array_keys($info),
+ 'type' => $info,
+ 'bind' => $bind,
+ 'pk' => $pk,
+ 'autoinc' => $autoinc,
+ ];
+ }
+
+ return $this->info[$schema];
+ }
+
+ /**
+ * 获取数据表信息
+ * @access public
+ * @param mixed $tableName 数据表名 留空自动获取
+ * @param string $fetch 获取信息类型 包括 fields type bind pk
+ * @return mixed
+ */
+ public function getTableInfo($tableName, string $fetch = '')
+ {
+ if (is_array($tableName)) {
+ $tableName = key($tableName) ?: current($tableName);
+ }
+
+ if (strpos($tableName, ',') || strpos($tableName, ')')) {
+ // 多表不获取字段信息
+ return [];
+ }
+
+ [$tableName] = explode(' ', $tableName);
+
+ $info = $this->getSchemaInfo($tableName);
+
+ return $fetch ? $info[$fetch] : $info;
+ }
+
+ /**
+ * 获取数据表的字段信息
+ * @access public
+ * @param string $tableName 数据表名
+ * @return array
+ */
+ public function getTableFieldsInfo(string $tableName): array
+ {
+ $fields = $this->getFields($tableName);
+ $info = [];
+
+ foreach ($fields as $key => $val) {
+ // 记录字段类型
+ $info[$key] = $this->getFieldType($val['type']);
+
+ if (!empty($val['primary'])) {
+ $pk[] = $key;
+ }
+
+ if (!empty($val['autoinc'])) {
+ $autoinc = $key;
+ }
+ }
+
+ if (isset($pk)) {
+ // 设置主键
+ $pk = count($pk) > 1 ? $pk : $pk[0];
+ $info['_pk'] = $pk;
+ }
+
+ if (isset($autoinc)) {
+ $info['_autoinc'] = $autoinc;
+ }
+
+ return $info;
+ }
+
+ /**
+ * 获取数据表的主键
+ * @access public
+ * @param mixed $tableName 数据表名
+ * @return string|array
+ */
+ public function getPk($tableName)
+ {
+ return $this->getTableInfo($tableName, 'pk');
+ }
+
+ /**
+ * 获取数据表的自增主键
+ * @access public
+ * @param mixed $tableName 数据表名
+ * @return string
+ */
+ public function getAutoInc($tableName)
+ {
+ return $this->getTableInfo($tableName, 'autoinc');
+ }
+
+ /**
+ * 获取数据表字段信息
+ * @access public
+ * @param mixed $tableName 数据表名
+ * @return array
+ */
+ public function getTableFields($tableName): array
+ {
+ return $this->getTableInfo($tableName, 'fields');
+ }
+
+ /**
+ * 获取数据表字段类型
+ * @access public
+ * @param mixed $tableName 数据表名
+ * @param string $field 字段名
+ * @return array|string
+ */
+ public function getFieldsType($tableName, string $field = null)
+ {
+ $result = $this->getTableInfo($tableName, 'type');
+
+ if ($field && isset($result[$field])) {
+ return $result[$field];
+ }
+
+ return $result;
+ }
+
+ /**
+ * 获取数据表绑定信息
+ * @access public
+ * @param mixed $tableName 数据表名
+ * @return array
+ */
+ public function getFieldsBind($tableName): array
+ {
+ return $this->getTableInfo($tableName, 'bind');
+ }
+
+ /**
+ * 连接数据库方法
+ * @access public
+ * @param array $config 连接参数
+ * @param integer $linkNum 连接序号
+ * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式)
+ * @return PDO
+ * @throws PDOException
+ */
+ public function connect(array $config = [], $linkNum = 0, $autoConnection = false): PDO
+ {
+ if (isset($this->links[$linkNum])) {
+ return $this->links[$linkNum];
+ }
+
+ if (empty($config)) {
+ $config = $this->config;
+ } else {
+ $config = array_merge($this->config, $config);
+ }
+
+ // 连接参数
+ if (isset($config['params']) && is_array($config['params'])) {
+ $params = $config['params'] + $this->params;
+ } else {
+ $params = $this->params;
+ }
+
+ // 记录当前字段属性大小写设置
+ $this->attrCase = $params[PDO::ATTR_CASE];
+
+ if (!empty($config['break_match_str'])) {
+ $this->breakMatchStr = array_merge($this->breakMatchStr, (array) $config['break_match_str']);
+ }
+
+ try {
+ if (empty($config['dsn'])) {
+ $config['dsn'] = $this->parseDsn($config);
+ }
+
+ $startTime = microtime(true);
+
+ $this->links[$linkNum] = $this->createPdo($config['dsn'], $config['username'], $config['password'], $params);
+
+ // SQL监控
+ if (!empty($config['trigger_sql'])) {
+ $this->trigger('CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']);
+ }
+
+ return $this->links[$linkNum];
+ } catch (\PDOException $e) {
+ if ($autoConnection) {
+ $this->db->log($e->getMessage(), 'error');
+ return $this->connect($autoConnection, $linkNum);
+ } else {
+ throw $e;
+ }
+ }
+ }
+
+ /**
+ * 视图查询
+ * @access public
+ * @param array $args
+ * @return BaseQuery
+ */
+ public function view(...$args)
+ {
+ return $this->newQuery()->view(...$args);
+ }
+
+ /**
+ * 创建PDO实例
+ * @param $dsn
+ * @param $username
+ * @param $password
+ * @param $params
+ * @return PDO
+ */
+ protected function createPdo($dsn, $username, $password, $params)
+ {
+ return new PDO($dsn, $username, $password, $params);
+ }
+
+ /**
+ * 释放查询结果
+ * @access public
+ */
+ public function free(): void
+ {
+ $this->PDOStatement = null;
+ }
+
+ /**
+ * 获取PDO对象
+ * @access public
+ * @return PDO|false
+ */
+ public function getPdo()
+ {
+ if (!$this->linkID) {
+ return false;
+ }
+
+ return $this->linkID;
+ }
+
+ /**
+ * 执行查询 使用生成器返回数据
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $sql sql指令
+ * @param array $bind 参数绑定
+ * @param Model|null $model 模型对象实例
+ * @param null $condition 查询条件
+ * @return \Generator
+ * @throws DbException
+ */
+ public function getCursor(BaseQuery $query, string $sql, array $bind = [], $model = null, $condition = null)
+ {
+ $this->queryPDOStatement($query, $sql, $bind);
+
+ // 返回结果集
+ while ($result = $this->PDOStatement->fetch($this->fetchType)) {
+ if ($model) {
+ yield $model->newInstance($result, $condition);
+ } else {
+ yield $result;
+ }
+ }
+ }
+
+ /**
+ * 执行查询 返回数据集
+ * @access public
+ * @param string $sql sql指令
+ * @param array $bind 参数绑定
+ * @param bool $master 主库读取
+ * @return array
+ * @throws DbException
+ */
+ public function query(string $sql, array $bind = [], bool $master = false): array
+ {
+ return $this->pdoQuery($this->newQuery(), $sql, $bind, $master);
+ }
+
+ /**
+ * 执行语句
+ * @access public
+ * @param string $sql sql指令
+ * @param array $bind 参数绑定
+ * @return int
+ * @throws DbException
+ */
+ public function execute(string $sql, array $bind = []): int
+ {
+ return $this->pdoExecute($this->newQuery(), $sql, $bind, true);
+ }
+
+ /**
+ * 执行查询 返回数据集
+ * @access protected
+ * @param BaseQuery $query 查询对象
+ * @param mixed $sql sql指令
+ * @param array $bind 参数绑定
+ * @param bool $master 主库读取
+ * @return array
+ * @throws DbException
+ */
+ protected function pdoQuery(BaseQuery $query, $sql, array $bind = [], bool $master = null): array
+ {
+ // 分析查询表达式
+ $query->parseOptions();
+
+ if ($query->getOptions('cache')) {
+ // 检查查询缓存
+ $cacheItem = $this->parseCache($query, $query->getOptions('cache'));
+ $key = $cacheItem->getKey();
+
+ $data = $this->cache->get($key);
+
+ if (null !== $data) {
+ return $data;
+ }
+ }
+
+ if ($sql instanceof Closure) {
+ $sql = $sql($query);
+ $bind = $query->getBind();
+ }
+
+ if (!isset($master)) {
+ $master = $query->getOptions('master') ? true : false;
+ }
+
+ $procedure = $query->getOptions('procedure') ? true : in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']);
+
+ $this->getPDOStatement($sql, $bind, $master, $procedure);
+
+ $resultSet = $this->getResult($procedure);
+
+ if (isset($cacheItem) && $resultSet) {
+ // 缓存数据集
+ $cacheItem->set($resultSet);
+ $this->cacheData($cacheItem);
+ }
+
+ return $resultSet;
+ }
+
+ /**
+ * 执行查询但只返回PDOStatement对象
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return \PDOStatement
+ * @throws DbException
+ */
+ public function pdo(BaseQuery $query): PDOStatement
+ {
+ $bind = $query->getBind();
+ // 生成查询SQL
+ $sql = $this->builder->select($query);
+
+ return $this->queryPDOStatement($query, $sql, $bind);
+ }
+
+ /**
+ * 执行查询但只返回PDOStatement对象
+ * @access public
+ * @param string $sql sql指令
+ * @param array $bind 参数绑定
+ * @param bool $master 是否在主服务器读操作
+ * @param bool $procedure 是否为存储过程调用
+ * @return PDOStatement
+ * @throws DbException
+ */
+ public function getPDOStatement(string $sql, array $bind = [], bool $master = false, bool $procedure = false): PDOStatement
+ {
+ try {
+ $this->initConnect($this->readMaster ?: $master);
+ // 记录SQL语句
+ $this->queryStr = $sql;
+ $this->bind = $bind;
+
+ $this->db->updateQueryTimes();
+ $this->queryStartTime = microtime(true);
+
+ // 预处理
+ $this->PDOStatement = $this->linkID->prepare($sql);
+
+ // 参数绑定
+ if ($procedure) {
+ $this->bindParam($bind);
+ } else {
+ $this->bindValue($bind);
+ }
+
+ // 执行查询
+ $this->PDOStatement->execute();
+
+ // SQL监控
+ if (!empty($this->config['trigger_sql'])) {
+ $this->trigger('', $master);
+ }
+
+ $this->reConnectTimes = 0;
+
+ return $this->PDOStatement;
+ } catch (\Throwable | \Exception $e) {
+ if ($this->transTimes > 0) {
+ // 事务活动中时不应该进行重试,应直接中断执行,防止造成污染。
+ if ($this->isBreak($e)) {
+ // 尝试对事务计数进行重置
+ $this->transTimes = 0;
+ }
+ } else {
+ if ($this->reConnectTimes < 4 && $this->isBreak($e)) {
+ ++$this->reConnectTimes;
+ return $this->close()->getPDOStatement($sql, $bind, $master, $procedure);
+ }
+ }
+
+ if ($e instanceof \PDOException) {
+ throw new PDOException($e, $this->config, $this->getLastsql());
+ } else {
+ throw $e;
+ }
+ }
+ }
+
+ /**
+ * 执行语句
+ * @access protected
+ * @param BaseQuery $query 查询对象
+ * @param string $sql sql指令
+ * @param array $bind 参数绑定
+ * @param bool $origin 是否原生查询
+ * @return int
+ * @throws DbException
+ */
+ protected function pdoExecute(BaseQuery $query, string $sql, array $bind = [], bool $origin = false): int
+ {
+ if ($origin) {
+ $query->parseOptions();
+ }
+
+ $this->queryPDOStatement($query->master(true), $sql, $bind);
+
+ if (!$origin && !empty($this->config['deploy']) && !empty($this->config['read_master'])) {
+ $this->readMaster = true;
+ }
+
+ $this->numRows = $this->PDOStatement->rowCount();
+
+ if ($query->getOptions('cache')) {
+ // 清理缓存数据
+ $cacheItem = $this->parseCache($query, $query->getOptions('cache'));
+ $key = $cacheItem->getKey();
+ $tag = $cacheItem->getTag();
+
+ if (isset($key) && $this->cache->has($key)) {
+ $this->cache->delete($key);
+ } elseif (!empty($tag) && method_exists($this->cache, 'tag')) {
+ $this->cache->tag($tag)->clear();
+ }
+ }
+
+ return $this->numRows;
+ }
+
+ /**
+ * @param BaseQuery $query
+ * @param string $sql
+ * @param array $bind
+ * @return PDOStatement
+ * @throws DbException
+ */
+ protected function queryPDOStatement(BaseQuery $query, string $sql, array $bind = []): PDOStatement
+ {
+ $options = $query->getOptions();
+ $master = !empty($options['master']) ? true : false;
+ $procedure = !empty($options['procedure']) ? true : in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']);
+
+ return $this->getPDOStatement($sql, $bind, $master, $procedure);
+ }
+
+ /**
+ * 查找单条记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return array
+ * @throws DbException
+ */
+ public function find(BaseQuery $query): array
+ {
+ // 事件回调
+ $result = $this->db->trigger('before_find', $query);
+
+ if (!$result) {
+ // 执行查询
+ $resultSet = $this->pdoQuery($query, function ($query) {
+ return $this->builder->select($query, true);
+ });
+
+ $result = $resultSet[0] ?? [];
+ }
+
+ return $result;
+ }
+
+ /**
+ * 使用游标查询记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return \Generator
+ */
+ public function cursor(BaseQuery $query)
+ {
+ // 分析查询表达式
+ $options = $query->parseOptions();
+
+ // 生成查询SQL
+ $sql = $this->builder->select($query);
+
+ $condition = $options['where']['AND'] ?? null;
+
+ // 执行查询操作
+ return $this->getCursor($query, $sql, $query->getBind(), $query->getModel(), $condition);
+ }
+
+ /**
+ * 查找记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return array
+ * @throws DbException
+ */
+ public function select(BaseQuery $query): array
+ {
+ $resultSet = $this->db->trigger('before_select', $query);
+
+ if (!$resultSet) {
+ // 执行查询操作
+ $resultSet = $this->pdoQuery($query, function ($query) {
+ return $this->builder->select($query);
+ });
+ }
+
+ return $resultSet;
+ }
+
+ /**
+ * 插入记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param boolean $getLastInsID 返回自增主键
+ * @return mixed
+ */
+ public function insert(BaseQuery $query, bool $getLastInsID = false)
+ {
+ // 分析查询表达式
+ $options = $query->parseOptions();
+
+ // 生成SQL语句
+ $sql = $this->builder->insert($query);
+
+ // 执行操作
+ $result = '' == $sql ? 0 : $this->pdoExecute($query, $sql, $query->getBind());
+
+ if ($result) {
+ $sequence = $options['sequence'] ?? null;
+ $lastInsId = $this->getLastInsID($query, $sequence);
+
+ $data = $options['data'];
+
+ if ($lastInsId) {
+ $pk = $query->getAutoInc();
+ if ($pk) {
+ $data[$pk] = $lastInsId;
+ }
+ }
+
+ $query->setOption('data', $data);
+
+ $this->db->trigger('after_insert', $query);
+
+ if ($getLastInsID && $lastInsId) {
+ return $lastInsId;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 批量插入记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param mixed $dataSet 数据集
+ * @param integer $limit 每次写入数据限制
+ * @return integer
+ * @throws \Exception
+ * @throws \Throwable
+ */
+ public function insertAll(BaseQuery $query, array $dataSet = [], int $limit = 0): int
+ {
+ if (!is_array(reset($dataSet))) {
+ return 0;
+ }
+
+ $options = $query->parseOptions();
+ $replace = !empty($options['replace']);
+
+ if (0 === $limit && count($dataSet) >= 5000) {
+ $limit = 1000;
+ }
+
+ if ($limit) {
+ // 分批写入 自动启动事务支持
+ $this->startTrans();
+
+ try {
+ $array = array_chunk($dataSet, $limit, true);
+ $count = 0;
+
+ foreach ($array as $item) {
+ $sql = $this->builder->insertAll($query, $item, $replace);
+ $count += $this->pdoExecute($query, $sql, $query->getBind());
+ }
+
+ // 提交事务
+ $this->commit();
+ } catch (\Exception | \Throwable $e) {
+ $this->rollback();
+ throw $e;
+ }
+
+ return $count;
+ }
+
+ $sql = $this->builder->insertAll($query, $dataSet, $replace);
+
+ return $this->pdoExecute($query, $sql, $query->getBind());
+ }
+
+ /**
+ * 通过Select方式插入记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param array $fields 要插入的数据表字段名
+ * @param string $table 要插入的数据表名
+ * @return integer
+ * @throws PDOException
+ */
+ public function selectInsert(BaseQuery $query, array $fields, string $table): int
+ {
+ // 分析查询表达式
+ $query->parseOptions();
+
+ $sql = $this->builder->selectInsert($query, $fields, $table);
+
+ return $this->pdoExecute($query, $sql, $query->getBind());
+ }
+
+ /**
+ * 更新记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return integer
+ * @throws PDOException
+ */
+ public function update(BaseQuery $query): int
+ {
+ $query->parseOptions();
+
+ // 生成UPDATE SQL语句
+ $sql = $this->builder->update($query);
+
+ // 执行操作
+ $result = '' == $sql ? 0 : $this->pdoExecute($query, $sql, $query->getBind());
+
+ if ($result) {
+ $this->db->trigger('after_update', $query);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return int
+ * @throws PDOException
+ */
+ public function delete(BaseQuery $query): int
+ {
+ // 分析查询表达式
+ $query->parseOptions();
+
+ // 生成删除SQL语句
+ $sql = $this->builder->delete($query);
+
+ // 执行操作
+ $result = $this->pdoExecute($query, $sql, $query->getBind());
+
+ if ($result) {
+ $this->db->trigger('after_delete', $query);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 得到某个字段的值
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $field 字段名
+ * @param mixed $default 默认值
+ * @param bool $one 返回一个值
+ * @return mixed
+ */
+ public function value(BaseQuery $query, string $field, $default = null, bool $one = true)
+ {
+ $options = $query->parseOptions();
+
+ if (isset($options['field'])) {
+ $query->removeOption('field');
+ }
+
+ if (isset($options['group'])) {
+ $query->group('');
+ }
+
+ $query->setOption('field', (array) $field);
+
+ if (!empty($options['cache'])) {
+ $cacheItem = $this->parseCache($query, $options['cache'], 'value');
+ $key = $cacheItem->getKey();
+
+ if ($this->cache->has($key)) {
+ return $this->cache->get($key);
+ }
+ }
+
+ // 生成查询SQL
+ $sql = $this->builder->select($query, $one);
+
+ if (isset($options['field'])) {
+ $query->setOption('field', $options['field']);
+ } else {
+ $query->removeOption('field');
+ }
+
+ if (isset($options['group'])) {
+ $query->setOption('group', $options['group']);
+ }
+
+ // 执行查询操作
+ $pdo = $this->getPDOStatement($sql, $query->getBind(), $options['master']);
+
+ $result = $pdo->fetchColumn();
+
+ if (isset($cacheItem)) {
+ // 缓存数据
+ $cacheItem->set($result);
+ $this->cacheData($cacheItem);
+ }
+
+ return false !== $result ? $result : $default;
+ }
+
+ /**
+ * 得到某个字段的值
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $aggregate 聚合方法
+ * @param mixed $field 字段名
+ * @param bool $force 强制转为数字类型
+ * @return mixed
+ */
+ public function aggregate(BaseQuery $query, string $aggregate, $field, bool $force = false)
+ {
+ if (is_string($field) && 0 === stripos($field, 'DISTINCT ')) {
+ [$distinct, $field] = explode(' ', $field);
+ }
+
+ $field = $aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $this->builder->parseKey($query, $field, true) . ') AS think_' . strtolower($aggregate);
+
+ $result = $this->value($query, $field, 0, false);
+
+ return $force ? (float) $result : $result;
+ }
+
+ /**
+ * 得到某个列的数组
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string|array $column 字段名 多个字段用逗号分隔
+ * @param string $key 索引
+ * @return array
+ */
+ public function column(BaseQuery $query, $column, string $key = ''): array
+ {
+ $options = $query->parseOptions();
+
+ if (isset($options['field'])) {
+ $query->removeOption('field');
+ }
+
+ if (empty($key) || trim($key) === '') {
+ $key = null;
+ }
+
+ if (\is_string($column)) {
+ $column = \trim($column);
+ if ('*' !== $column) {
+ $column = \array_map('\trim', \explode(',', $column));
+ }
+ } elseif (\is_array($column)) {
+ if (\in_array('*', $column)) {
+ $column = '*';
+ }
+ } else {
+ throw new DbException('not support type');
+ }
+
+ $field = $column;
+ if ('*' !== $column && $key && !\in_array($key, $column)) {
+ $field[] = $key;
+ }
+
+ $query->setOption('field', $field);
+
+ if (!empty($options['cache'])) {
+ // 判断查询缓存
+ $cacheItem = $this->parseCache($query, $options['cache'], 'column');
+ $name = $cacheItem->getKey();
+
+ if ($this->cache->has($name)) {
+ return $this->cache->get($name);
+ }
+ }
+
+ // 生成查询SQL
+ $sql = $this->builder->select($query);
+
+ if (isset($options['field'])) {
+ $query->setOption('field', $options['field']);
+ } else {
+ $query->removeOption('field');
+ }
+
+ // 执行查询操作
+ $pdo = $this->getPDOStatement($sql, $query->getBind(), $options['master']);
+ $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC);
+
+ if (is_string($key) && strpos($key, '.')) {
+ [$alias, $key] = explode('.', $key);
+ }
+
+ if (empty($resultSet)) {
+ $result = [];
+ } elseif ('*' !== $column && \count($column) === 1) {
+ $column = \array_shift($column);
+ if (\strpos($column, ' ')) {
+ $column = \substr(\strrchr(\trim($column), ' '), 1);
+ }
+
+ if (\strpos($column, '.')) {
+ [$alias, $column] = \explode('.', $column);
+ }
+
+ $result = \array_column($resultSet, $column, $key);
+ } elseif ($key) {
+ $result = \array_column($resultSet, null, $key);
+ } else {
+ $result = $resultSet;
+ }
+
+ if (isset($cacheItem)) {
+ // 缓存数据
+ $cacheItem->set($result);
+ $this->cacheData($cacheItem);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 根据参数绑定组装最终的SQL语句 便于调试
+ * @access public
+ * @param string $sql 带参数绑定的sql语句
+ * @param array $bind 参数绑定列表
+ * @return string
+ */
+ public function getRealSql(string $sql, array $bind = []): string
+ {
+ foreach ($bind as $key => $val) {
+ $value = strval(is_array($val) ? $val[0] : $val);
+ $type = is_array($val) ? $val[1] : PDO::PARAM_STR;
+
+ if (self::PARAM_FLOAT == $type || PDO::PARAM_STR == $type) {
+ $value = '\'' . addslashes($value) . '\'';
+ } elseif (PDO::PARAM_INT == $type && '' === $value) {
+ $value = '0';
+ }
+
+ // 判断占位符
+ $sql = is_numeric($key) ?
+ substr_replace($sql, $value, strpos($sql, '?'), 1) :
+ substr_replace($sql, $value, strpos($sql, ':' . $key), strlen(':' . $key));
+ }
+
+ return rtrim($sql);
+ }
+
+ /**
+ * 参数绑定
+ * 支持 ['name'=>'value','id'=>123] 对应命名占位符
+ * 或者 ['value',123] 对应问号占位符
+ * @access public
+ * @param array $bind 要绑定的参数列表
+ * @return void
+ * @throws BindParamException
+ */
+ protected function bindValue(array $bind = []): void
+ {
+ foreach ($bind as $key => $val) {
+ // 占位符
+ $param = is_numeric($key) ? $key + 1 : ':' . $key;
+
+ if (is_array($val)) {
+ if (PDO::PARAM_INT == $val[1] && '' === $val[0]) {
+ $val[0] = 0;
+ } elseif (self::PARAM_FLOAT == $val[1]) {
+ $val[0] = is_string($val[0]) ? (float) $val[0] : $val[0];
+ $val[1] = PDO::PARAM_STR;
+ }
+
+ $result = $this->PDOStatement->bindValue($param, $val[0], $val[1]);
+ } else {
+ $result = $this->PDOStatement->bindValue($param, $val);
+ }
+
+ if (!$result) {
+ throw new BindParamException(
+ "Error occurred when binding parameters '{$param}'",
+ $this->config,
+ $this->getLastsql(),
+ $bind
+ );
+ }
+ }
+ }
+
+ /**
+ * 存储过程的输入输出参数绑定
+ * @access public
+ * @param array $bind 要绑定的参数列表
+ * @return void
+ * @throws BindParamException
+ */
+ protected function bindParam(array $bind): void
+ {
+ foreach ($bind as $key => $val) {
+ $param = is_numeric($key) ? $key + 1 : ':' . $key;
+
+ if (is_array($val)) {
+ array_unshift($val, $param);
+ $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val);
+ } else {
+ $result = $this->PDOStatement->bindValue($param, $val);
+ }
+
+ if (!$result) {
+ $param = array_shift($val);
+
+ throw new BindParamException(
+ "Error occurred when binding parameters '{$param}'",
+ $this->config,
+ $this->getLastsql(),
+ $bind
+ );
+ }
+ }
+ }
+
+ /**
+ * 获得数据集数组
+ * @access protected
+ * @param bool $procedure 是否存储过程
+ * @return array
+ */
+ protected function getResult(bool $procedure = false): array
+ {
+ if ($procedure) {
+ // 存储过程返回结果
+ return $this->procedure();
+ }
+
+ $result = $this->PDOStatement->fetchAll($this->fetchType);
+
+ $this->numRows = count($result);
+
+ return $result;
+ }
+
+ /**
+ * 获得存储过程数据集
+ * @access protected
+ * @return array
+ */
+ protected function procedure(): array
+ {
+ $item = [];
+
+ do {
+ $result = $this->getResult();
+ if (!empty($result)) {
+ $item[] = $result;
+ }
+ } while ($this->PDOStatement->nextRowset());
+
+ $this->numRows = count($item);
+
+ return $item;
+ }
+
+ /**
+ * 执行数据库事务
+ * @access public
+ * @param callable $callback 数据操作方法回调
+ * @return mixed
+ * @throws PDOException
+ * @throws \Exception
+ * @throws \Throwable
+ */
+ public function transaction(callable $callback)
+ {
+ $this->startTrans();
+
+ try {
+ $result = null;
+ if (is_callable($callback)) {
+ $result = $callback($this);
+ }
+
+ $this->commit();
+ return $result;
+ } catch (\Exception | \Throwable $e) {
+ $this->rollback();
+ throw $e;
+ }
+ }
+
+ /**
+ * 启动事务
+ * @access public
+ * @return void
+ * @throws \PDOException
+ * @throws \Exception
+ */
+ public function startTrans(): void
+ {
+ try {
+ $this->initConnect(true);
+
+ ++$this->transTimes;
+
+ if (1 == $this->transTimes) {
+ $this->linkID->beginTransaction();
+ } elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
+ $this->linkID->exec(
+ $this->parseSavepoint('trans' . $this->transTimes)
+ );
+ }
+ $this->reConnectTimes = 0;
+ } catch (\Throwable | \Exception $e) {
+ if ($this->transTimes === 1 && $this->reConnectTimes < 4 && $this->isBreak($e)) {
+ --$this->transTimes;
+ ++$this->reConnectTimes;
+ $this->close()->startTrans();
+ } else {
+ if ($this->isBreak($e)) {
+ // 尝试对事务计数进行重置
+ $this->transTimes = 0;
+ }
+ throw $e;
+ }
+ }
+ }
+
+ /**
+ * 用于非自动提交状态下面的查询提交
+ * @access public
+ * @return void
+ * @throws \PDOException
+ */
+ public function commit(): void
+ {
+ $this->initConnect(true);
+
+ if (1 == $this->transTimes) {
+ $this->linkID->commit();
+ }
+
+ --$this->transTimes;
+ }
+
+ /**
+ * 事务回滚
+ * @access public
+ * @return void
+ * @throws \PDOException
+ */
+ public function rollback(): void
+ {
+ $this->initConnect(true);
+
+ if (1 == $this->transTimes) {
+ $this->linkID->rollBack();
+ } elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
+ $this->linkID->exec(
+ $this->parseSavepointRollBack('trans' . $this->transTimes)
+ );
+ }
+
+ $this->transTimes = max(0, $this->transTimes - 1);
+ }
+
+ /**
+ * 是否支持事务嵌套
+ * @return bool
+ */
+ protected function supportSavepoint(): bool
+ {
+ return false;
+ }
+
+ /**
+ * 生成定义保存点的SQL
+ * @access protected
+ * @param string $name 标识
+ * @return string
+ */
+ protected function parseSavepoint(string $name): string
+ {
+ return 'SAVEPOINT ' . $name;
+ }
+
+ /**
+ * 生成回滚到保存点的SQL
+ * @access protected
+ * @param string $name 标识
+ * @return string
+ */
+ protected function parseSavepointRollBack(string $name): string
+ {
+ return 'ROLLBACK TO SAVEPOINT ' . $name;
+ }
+
+ /**
+ * 批处理执行SQL语句
+ * 批处理的指令都认为是execute操作
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param array $sqlArray SQL批处理指令
+ * @param array $bind 参数绑定
+ * @return bool
+ */
+ public function batchQuery(BaseQuery $query, array $sqlArray = [], array $bind = []): bool
+ {
+ // 自动启动事务支持
+ $this->startTrans();
+
+ try {
+ foreach ($sqlArray as $sql) {
+ $this->pdoExecute($query, $sql, $bind);
+ }
+ // 提交事务
+ $this->commit();
+ } catch (\Exception $e) {
+ $this->rollback();
+ throw $e;
+ }
+
+ return true;
+ }
+
+ /**
+ * 关闭数据库(或者重新连接)
+ * @access public
+ * @return $this
+ */
+ public function close()
+ {
+ $this->linkID = null;
+ $this->linkWrite = null;
+ $this->linkRead = null;
+ $this->links = [];
+ $this->transTimes = 0;
+
+ $this->free();
+
+ return $this;
+ }
+
+ /**
+ * 是否断线
+ * @access protected
+ * @param \PDOException|\Exception $e 异常对象
+ * @return bool
+ */
+ protected function isBreak($e): bool
+ {
+ if (!$this->config['break_reconnect']) {
+ return false;
+ }
+
+ $error = $e->getMessage();
+
+ foreach ($this->breakMatchStr as $msg) {
+ if (false !== stripos($error, $msg)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 获取最近一次查询的sql语句
+ * @access public
+ * @return string
+ */
+ public function getLastSql(): string
+ {
+ return $this->getRealSql($this->queryStr, $this->bind);
+ }
+
+ /**
+ * 获取最近插入的ID
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $sequence 自增序列名
+ * @return mixed
+ */
+ public function getLastInsID(BaseQuery $query, string $sequence = null)
+ {
+ try {
+ $insertId = $this->linkID->lastInsertId($sequence);
+ } catch (\Exception $e) {
+ $insertId = '';
+ }
+
+ return $this->autoInsIDType($query, $insertId);
+ }
+
+ /**
+ * 获取最近插入的ID
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $insertId 自增ID
+ * @return mixed
+ */
+ protected function autoInsIDType(BaseQuery $query, string $insertId)
+ {
+ $pk = $query->getAutoInc();
+
+ if ($pk) {
+ $type = $this->getFieldBindType($pk);
+
+ if (PDO::PARAM_INT == $type) {
+ $insertId = (int) $insertId;
+ } elseif (self::PARAM_FLOAT == $type) {
+ $insertId = (float) $insertId;
+ }
+ }
+
+ return $insertId;
+ }
+
+ /**
+ * 获取最近的错误信息
+ * @access public
+ * @return string
+ */
+ public function getError(): string
+ {
+ if ($this->PDOStatement) {
+ $error = $this->PDOStatement->errorInfo();
+ $error = $error[1] . ':' . $error[2];
+ } else {
+ $error = '';
+ }
+
+ if ('' != $this->queryStr) {
+ $error .= "\n [ SQL语句 ] : " . $this->getLastsql();
+ }
+
+ return $error;
+ }
+
+ /**
+ * 初始化数据库连接
+ * @access protected
+ * @param boolean $master 是否主服务器
+ * @return void
+ */
+ protected function initConnect(bool $master = true): void
+ {
+ if (!empty($this->config['deploy'])) {
+ // 采用分布式数据库
+ if ($master || $this->transTimes) {
+ if (!$this->linkWrite) {
+ $this->linkWrite = $this->multiConnect(true);
+ }
+
+ $this->linkID = $this->linkWrite;
+ } else {
+ if (!$this->linkRead) {
+ $this->linkRead = $this->multiConnect(false);
+ }
+
+ $this->linkID = $this->linkRead;
+ }
+ } elseif (!$this->linkID) {
+ // 默认单数据库
+ $this->linkID = $this->connect();
+ }
+ }
+
+ /**
+ * 连接分布式服务器
+ * @access protected
+ * @param boolean $master 主服务器
+ * @return PDO
+ */
+ protected function multiConnect(bool $master = false): PDO
+ {
+ $config = [];
+
+ // 分布式数据库配置解析
+ foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) {
+ $config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $this->config[$name];
+ }
+
+ // 主服务器序号
+ $m = floor(mt_rand(0, $this->config['master_num'] - 1));
+
+ if ($this->config['rw_separate']) {
+ // 主从式采用读写分离
+ if ($master) // 主服务器写入
+ {
+ $r = $m;
+ } elseif (is_numeric($this->config['slave_no'])) {
+ // 指定服务器读
+ $r = $this->config['slave_no'];
+ } else {
+ // 读操作连接从服务器 每次随机连接的数据库
+ $r = floor(mt_rand($this->config['master_num'], count($config['hostname']) - 1));
+ }
+ } else {
+ // 读写操作不区分服务器 每次随机连接的数据库
+ $r = floor(mt_rand(0, count($config['hostname']) - 1));
+ }
+ $dbMaster = false;
+
+ if ($m != $r) {
+ $dbMaster = [];
+ foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) {
+ $dbMaster[$name] = $config[$name][$m] ?? $config[$name][0];
+ }
+ }
+
+ $dbConfig = [];
+
+ foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) {
+ $dbConfig[$name] = $config[$name][$r] ?? $config[$name][0];
+ }
+
+ return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster);
+ }
+
+ /**
+ * 启动XA事务
+ * @access public
+ * @param string $xid XA事务id
+ * @return void
+ */
+ public function startTransXa(string $xid)
+ {}
+
+ /**
+ * 预编译XA事务
+ * @access public
+ * @param string $xid XA事务id
+ * @return void
+ */
+ public function prepareXa(string $xid)
+ {}
+
+ /**
+ * 提交XA事务
+ * @access public
+ * @param string $xid XA事务id
+ * @return void
+ */
+ public function commitXa(string $xid)
+ {}
+
+ /**
+ * 回滚XA事务
+ * @access public
+ * @param string $xid XA事务id
+ * @return void
+ */
+ public function rollbackXa(string $xid)
+ {}
+}
diff --git a/vendor/topthink/think-orm/src/db/Query.php b/vendor/topthink/think-orm/src/db/Query.php
new file mode 100644
index 000000000..80e01cd9b
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/Query.php
@@ -0,0 +1,451 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use PDOStatement;
+use think\helper\Str;
+
+/**
+ * PDO数据查询类
+ */
+class Query extends BaseQuery
+{
+ use concern\JoinAndViewQuery;
+ use concern\ParamsBind;
+ use concern\TableFieldInfo;
+
+ /**
+ * 表达式方式指定Field排序
+ * @access public
+ * @param string $field 排序字段
+ * @param array $bind 参数绑定
+ * @return $this
+ */
+ public function orderRaw(string $field, array $bind = [])
+ {
+ $this->options['order'][] = new Raw($field, $bind);
+
+ return $this;
+ }
+
+ /**
+ * 表达式方式指定查询字段
+ * @access public
+ * @param string $field 字段名
+ * @return $this
+ */
+ public function fieldRaw(string $field)
+ {
+ $this->options['field'][] = new Raw($field);
+
+ return $this;
+ }
+
+ /**
+ * 指定Field排序 orderField('id',[1,2,3],'desc')
+ * @access public
+ * @param string $field 排序字段
+ * @param array $values 排序值
+ * @param string $order 排序 desc/asc
+ * @return $this
+ */
+ public function orderField(string $field, array $values, string $order = '')
+ {
+ if (!empty($values)) {
+ $values['sort'] = $order;
+
+ $this->options['order'][$field] = $values;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 随机排序
+ * @access public
+ * @return $this
+ */
+ public function orderRand()
+ {
+ $this->options['order'][] = '[rand]';
+ return $this;
+ }
+
+ /**
+ * 使用表达式设置数据
+ * @access public
+ * @param string $field 字段名
+ * @param string $value 字段值
+ * @return $this
+ */
+ public function exp(string $field, string $value)
+ {
+ $this->options['data'][$field] = new Raw($value);
+ return $this;
+ }
+
+ /**
+ * 表达式方式指定当前操作的数据表
+ * @access public
+ * @param mixed $table 表名
+ * @return $this
+ */
+ public function tableRaw(string $table)
+ {
+ $this->options['table'] = new Raw($table);
+
+ return $this;
+ }
+
+ /**
+ * 获取执行的SQL语句而不进行实际的查询
+ * @access public
+ * @param bool $fetch 是否返回sql
+ * @return $this|Fetch
+ */
+ public function fetchSql(bool $fetch = true)
+ {
+ $this->options['fetch_sql'] = $fetch;
+
+ if ($fetch) {
+ return new Fetch($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 批处理执行SQL语句
+ * 批处理的指令都认为是execute操作
+ * @access public
+ * @param array $sql SQL批处理指令
+ * @return bool
+ */
+ public function batchQuery(array $sql = []): bool
+ {
+ return $this->connection->batchQuery($this, $sql);
+ }
+
+ /**
+ * USING支持 用于多表删除
+ * @access public
+ * @param mixed $using USING
+ * @return $this
+ */
+ public function using($using)
+ {
+ $this->options['using'] = $using;
+ return $this;
+ }
+
+ /**
+ * 存储过程调用
+ * @access public
+ * @param bool $procedure 是否为存储过程查询
+ * @return $this
+ */
+ public function procedure(bool $procedure = true)
+ {
+ $this->options['procedure'] = $procedure;
+ return $this;
+ }
+
+ /**
+ * 指定group查询
+ * @access public
+ * @param string|array $group GROUP
+ * @return $this
+ */
+ public function group($group)
+ {
+ $this->options['group'] = $group;
+ return $this;
+ }
+
+ /**
+ * 指定having查询
+ * @access public
+ * @param string $having having
+ * @return $this
+ */
+ public function having(string $having)
+ {
+ $this->options['having'] = $having;
+ return $this;
+ }
+
+ /**
+ * 指定distinct查询
+ * @access public
+ * @param bool $distinct 是否唯一
+ * @return $this
+ */
+ public function distinct(bool $distinct = true)
+ {
+ $this->options['distinct'] = $distinct;
+ return $this;
+ }
+
+ /**
+ * 指定强制索引
+ * @access public
+ * @param string $force 索引名称
+ * @return $this
+ */
+ public function force(string $force)
+ {
+ $this->options['force'] = $force;
+ return $this;
+ }
+
+ /**
+ * 查询注释
+ * @access public
+ * @param string $comment 注释
+ * @return $this
+ */
+ public function comment(string $comment)
+ {
+ $this->options['comment'] = $comment;
+ return $this;
+ }
+
+ /**
+ * 设置是否REPLACE
+ * @access public
+ * @param bool $replace 是否使用REPLACE写入数据
+ * @return $this
+ */
+ public function replace(bool $replace = true)
+ {
+ $this->options['replace'] = $replace;
+ return $this;
+ }
+
+ /**
+ * 设置当前查询所在的分区
+ * @access public
+ * @param string|array $partition 分区名称
+ * @return $this
+ */
+ public function partition($partition)
+ {
+ $this->options['partition'] = $partition;
+ return $this;
+ }
+
+ /**
+ * 设置DUPLICATE
+ * @access public
+ * @param array|string|Raw $duplicate DUPLICATE信息
+ * @return $this
+ */
+ public function duplicate($duplicate)
+ {
+ $this->options['duplicate'] = $duplicate;
+ return $this;
+ }
+
+ /**
+ * 设置查询的额外参数
+ * @access public
+ * @param string $extra 额外信息
+ * @return $this
+ */
+ public function extra(string $extra)
+ {
+ $this->options['extra'] = $extra;
+ return $this;
+ }
+
+ /**
+ * 创建子查询SQL
+ * @access public
+ * @param bool $sub 是否添加括号
+ * @return string
+ * @throws Exception
+ */
+ public function buildSql(bool $sub = true): string
+ {
+ return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select();
+ }
+
+ /**
+ * 获取当前数据表的主键
+ * @access public
+ * @return string|array
+ */
+ public function getPk()
+ {
+ if (empty($this->pk)) {
+ $this->pk = $this->connection->getPk($this->getTable());
+ }
+
+ return $this->pk;
+ }
+
+ /**
+ * 指定数据表自增主键
+ * @access public
+ * @param string $autoinc 自增键
+ * @return $this
+ */
+ public function autoinc(string $autoinc)
+ {
+ $this->autoinc = $autoinc;
+ return $this;
+ }
+
+ /**
+ * 获取当前数据表的自增主键
+ * @access public
+ * @return string|null
+ */
+ public function getAutoInc()
+ {
+ $tableName = $this->getTable();
+
+ if (empty($this->autoinc) && $tableName) {
+ $this->autoinc = $this->connection->getAutoInc($tableName);
+ }
+
+ return $this->autoinc;
+ }
+
+ /**
+ * 字段值增长
+ * @access public
+ * @param string $field 字段名
+ * @param float $step 增长值
+ * @return $this
+ */
+ public function inc(string $field, float $step = 1)
+ {
+ $this->options['data'][$field] = ['INC', $step];
+
+ return $this;
+ }
+
+ /**
+ * 字段值减少
+ * @access public
+ * @param string $field 字段名
+ * @param float $step 增长值
+ * @return $this
+ */
+ public function dec(string $field, float $step = 1)
+ {
+ $this->options['data'][$field] = ['DEC', $step];
+ return $this;
+ }
+
+ /**
+ * 获取当前的查询标识
+ * @access public
+ * @param mixed $data 要序列化的数据
+ * @return string
+ */
+ public function getQueryGuid($data = null): string
+ {
+ return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true)) . serialize($this->getBind(false)));
+ }
+
+ /**
+ * 执行查询但只返回PDOStatement对象
+ * @access public
+ * @return PDOStatement
+ */
+ public function getPdo(): PDOStatement
+ {
+ return $this->connection->pdo($this);
+ }
+
+ /**
+ * 使用游标查找记录
+ * @access public
+ * @param mixed $data 数据
+ * @return \Generator
+ */
+ public function cursor($data = null)
+ {
+ if (!is_null($data)) {
+ // 主键条件分析
+ $this->parsePkWhere($data);
+ }
+
+ $this->options['data'] = $data;
+
+ $connection = clone $this->connection;
+
+ return $connection->cursor($this);
+ }
+
+ /**
+ * 分批数据返回处理
+ * @access public
+ * @param integer $count 每次处理的数据数量
+ * @param callable $callback 处理回调方法
+ * @param string|array $column 分批处理的字段名
+ * @param string $order 字段排序
+ * @return bool
+ * @throws Exception
+ */
+ public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
+ {
+ $options = $this->getOptions();
+ $column = $column ?: $this->getPk();
+
+ if (isset($options['order'])) {
+ unset($options['order']);
+ }
+
+ $bind = $this->bind;
+
+ if (is_array($column)) {
+ $times = 1;
+ $query = $this->options($options)->page($times, $count);
+ } else {
+ $query = $this->options($options)->limit($count);
+
+ if (strpos($column, '.')) {
+ [$alias, $key] = explode('.', $column);
+ } else {
+ $key = $column;
+ }
+ }
+
+ $resultSet = $query->order($column, $order)->select();
+
+ while (count($resultSet) > 0) {
+ if (false === call_user_func($callback, $resultSet)) {
+ return false;
+ }
+
+ if (isset($times)) {
+ $times++;
+ $query = $this->options($options)->page($times, $count);
+ } else {
+ $end = $resultSet->pop();
+ $lastId = is_array($end) ? $end[$key] : $end->getData($key);
+
+ $query = $this->options($options)
+ ->limit($count)
+ ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
+ }
+
+ $resultSet = $query->bind($bind)->order($column, $order)->select();
+ }
+
+ return true;
+ }
+}
diff --git a/thinkphp/library/think/db/Expression.php b/vendor/topthink/think-orm/src/db/Raw.php
old mode 100755
new mode 100644
similarity index 65%
rename from thinkphp/library/think/db/Expression.php
rename to vendor/topthink/think-orm/src/db/Raw.php
index f1b92abd7..833fbf08c
--- a/thinkphp/library/think/db/Expression.php
+++ b/vendor/topthink/think-orm/src/db/Raw.php
@@ -2,16 +2,20 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\db;
-class Expression
+/**
+ * SQL Raw
+ */
+class Raw
{
/**
* 查询表达式
@@ -20,15 +24,24 @@ class Expression
*/
protected $value;
+ /**
+ * 参数绑定
+ *
+ * @var array
+ */
+ protected $bind = [];
+
/**
* 创建一个查询表达式
*
* @param string $value
+ * @param array $bind
* @return void
*/
- public function __construct($value)
+ public function __construct(string $value, array $bind = [])
{
$this->value = $value;
+ $this->bind = $bind;
}
/**
@@ -36,11 +49,21 @@ class Expression
*
* @return string
*/
- public function getValue()
+ public function getValue(): string
{
return $this->value;
}
+ /**
+ * 获取参数绑定
+ *
+ * @return string
+ */
+ public function getBind(): array
+ {
+ return $this->bind;
+ }
+
public function __toString()
{
return (string) $this->value;
diff --git a/thinkphp/library/think/db/Where.php b/vendor/topthink/think-orm/src/db/Where.php
old mode 100755
new mode 100644
similarity index 83%
rename from thinkphp/library/think/db/Where.php
rename to vendor/topthink/think-orm/src/db/Where.php
index 9132e5461..088046089
--- a/thinkphp/library/think/db/Where.php
+++ b/vendor/topthink/think-orm/src/db/Where.php
@@ -2,17 +2,21 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\db;
use ArrayAccess;
+/**
+ * 数组查询对象
+ */
class Where implements ArrayAccess
{
/**
@@ -22,7 +26,7 @@ class Where implements ArrayAccess
protected $where = [];
/**
- * 是否需要增加括号
+ * 是否需要把查询条件两边增加括号
* @var bool
*/
protected $enclose = false;
@@ -33,7 +37,7 @@ class Where implements ArrayAccess
* @param array $where 查询条件数组
* @param bool $enclose 是否增加括号
*/
- public function __construct(array $where = [], $enclose = false)
+ public function __construct(array $where = [], bool $enclose = false)
{
$this->where = $where;
$this->enclose = $enclose;
@@ -45,7 +49,7 @@ class Where implements ArrayAccess
* @param bool $enclose
* @return $this
*/
- public function enclose($enclose = true)
+ public function enclose(bool $enclose = true)
{
$this->enclose = $enclose;
return $this;
@@ -56,12 +60,12 @@ class Where implements ArrayAccess
* @access public
* @return array
*/
- public function parse()
+ public function parse(): array
{
$where = [];
foreach ($this->where as $key => $val) {
- if ($val instanceof Expression) {
+ if ($val instanceof Raw) {
$where[] = [$key, 'exp', $val];
} elseif (is_null($val)) {
$where[] = [$key, 'NULL', ''];
@@ -82,21 +86,21 @@ class Where implements ArrayAccess
* @param array $where 查询条件
* @return array
*/
- protected function parseItem($field, $where = [])
+ protected function parseItem(string $field, array $where = []): array
{
$op = $where[0];
- $condition = isset($where[1]) ? $where[1] : null;
+ $condition = $where[1] ?? null;
if (is_array($op)) {
// 同一字段多条件查询
array_unshift($where, $field);
} elseif (is_null($condition)) {
- if (in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
+ if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
// null查询
$where = [$field, $op, ''];
- } elseif (in_array($op, ['=', 'eq', 'EQ', null], true)) {
+ } elseif (is_null($op) || '=' == $op) {
$where = [$field, 'NULL', ''];
- } elseif (in_array($op, ['<>', 'neq', 'NEQ'], true)) {
+ } elseif ('<>' == $op) {
$where = [$field, 'NOTNULL', ''];
} else {
// 字段相等查询
@@ -129,14 +133,14 @@ class Where implements ArrayAccess
*/
public function __get($name)
{
- return isset($this->where[$name]) ? $this->where[$name] : null;
+ return $this->where[$name] ?? null;
}
/**
* 检测数据对象的值
* @access public
* @param string $name 名称
- * @return boolean
+ * @return bool
*/
public function __isset($name)
{
diff --git a/vendor/topthink/think-orm/src/db/builder/Mongo.php b/vendor/topthink/think-orm/src/db/builder/Mongo.php
new file mode 100644
index 000000000..823156bc3
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/builder/Mongo.php
@@ -0,0 +1,675 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+namespace think\db\builder;
+
+use MongoDB\BSON\Javascript;
+use MongoDB\BSON\ObjectID;
+use MongoDB\BSON\Regex;
+use MongoDB\Driver\BulkWrite;
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Exception\InvalidArgumentException;
+use MongoDB\Driver\Query as MongoQuery;
+use think\db\connector\Mongo as Connection;
+use think\db\exception\DbException as Exception;
+use think\db\Mongo as Query;
+
+class Mongo
+{
+ // connection对象实例
+ protected $connection;
+ // 最后插入ID
+ protected $insertId = [];
+ // 查询表达式
+ protected $exp = ['<>' => 'ne', '=' => 'eq', '>' => 'gt', '>=' => 'gte', '<' => 'lt', '<=' => 'lte', 'in' => 'in', 'not in' => 'nin', 'nin' => 'nin', 'mod' => 'mod', 'exists' => 'exists', 'null' => 'null', 'notnull' => 'not null', 'not null' => 'not null', 'regex' => 'regex', 'type' => 'type', 'all' => 'all', '> time' => '> time', '< time' => '< time', 'between' => 'between', 'not between' => 'not between', 'between time' => 'between time', 'not between time' => 'not between time', 'notbetween time' => 'not between time', 'like' => 'like', 'near' => 'near', 'size' => 'size'];
+
+ /**
+ * 架构函数
+ * @access public
+ * @param Connection $connection 数据库连接对象实例
+ */
+ public function __construct(Connection $connection)
+ {
+ $this->connection = $connection;
+ }
+
+ /**
+ * 获取当前的连接对象实例
+ * @access public
+ * @return Connection
+ */
+ public function getConnection(): Connection
+ {
+ return $this->connection;
+ }
+
+ /**
+ * key分析
+ * @access protected
+ * @param string $key
+ * @return string
+ */
+ protected function parseKey(Query $query, string $key): string
+ {
+ if (0 === strpos($key, '__TABLE__.')) {
+ [$collection, $key] = explode('.', $key, 2);
+ }
+
+ if ('id' == $key && $this->connection->getConfig('pk_convert_id')) {
+ $key = '_id';
+ }
+
+ return trim($key);
+ }
+
+ /**
+ * value分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $value
+ * @param string $field
+ * @return string
+ */
+ protected function parseValue(Query $query, $value, $field = '')
+ {
+ if ('_id' == $field && 'ObjectID' == $this->connection->getConfig('pk_type') && is_string($value)) {
+ try {
+ return new ObjectID($value);
+ } catch (InvalidArgumentException $e) {
+ return new ObjectID();
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * insert数据分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param array $data 数据
+ * @return array
+ */
+ protected function parseData(Query $query, array $data): array
+ {
+ if (empty($data)) {
+ return [];
+ }
+
+ $result = [];
+
+ foreach ($data as $key => $val) {
+ $item = $this->parseKey($query, $key);
+
+ if (is_object($val)) {
+ $result[$item] = $val;
+ } elseif (isset($val[0]) && 'exp' == $val[0]) {
+ $result[$item] = $val[1];
+ } elseif (is_null($val)) {
+ $result[$item] = 'NULL';
+ } else {
+ $result[$item] = $this->parseValue($query, $val, $key);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Set数据分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param array $data 数据
+ * @return array
+ */
+ protected function parseSet(Query $query, array $data): array
+ {
+ if (empty($data)) {
+ return [];
+ }
+
+ $result = [];
+
+ foreach ($data as $key => $val) {
+ $item = $this->parseKey($query, $key);
+
+ if (is_array($val) && isset($val[0]) && is_string($val[0]) && 0 === strpos($val[0], '$')) {
+ $result[$val[0]][$item] = $this->parseValue($query, $val[1], $key);
+ } else {
+ $result['$set'][$item] = $this->parseValue($query, $val, $key);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 生成查询过滤条件
+ * @access public
+ * @param Query $query 查询对象
+ * @param mixed $where
+ * @return array
+ */
+ public function parseWhere(Query $query, array $where): array
+ {
+ if (empty($where)) {
+ $where = [];
+ }
+
+ $filter = [];
+ foreach ($where as $logic => $val) {
+ $logic = '$' . strtolower($logic);
+ foreach ($val as $field => $value) {
+ if (is_array($value)) {
+ if (key($value) !== 0) {
+ throw new Exception('where express error:' . var_export($value, true));
+ }
+ $field = array_shift($value);
+ } elseif (!($value instanceof \Closure)) {
+ throw new Exception('where express error:' . var_export($value, true));
+ }
+
+ if ($value instanceof \Closure) {
+ // 使用闭包查询
+ $query = new Query($this->connection);
+ call_user_func_array($value, [ & $query]);
+ $filter[$logic][] = $this->parseWhere($query, $query->getOptions('where'));
+ } else {
+ if (strpos($field, '|')) {
+ // 不同字段使用相同查询条件(OR)
+ $array = explode('|', $field);
+ foreach ($array as $k) {
+ $filter['$or'][] = $this->parseWhereItem($query, $k, $value);
+ }
+ } elseif (strpos($field, '&')) {
+ // 不同字段使用相同查询条件(AND)
+ $array = explode('&', $field);
+ foreach ($array as $k) {
+ $filter['$and'][] = $this->parseWhereItem($query, $k, $value);
+ }
+ } else {
+ // 对字段使用表达式查询
+ $field = is_string($field) ? $field : '';
+ $filter[$logic][] = $this->parseWhereItem($query, $field, $value);
+ }
+ }
+ }
+ }
+
+ $options = $query->getOptions();
+ if (!empty($options['soft_delete'])) {
+ // 附加软删除条件
+ [$field, $condition] = $options['soft_delete'];
+ $filter['$and'][] = $this->parseWhereItem($query, $field, $condition);
+ }
+
+ return $filter;
+ }
+
+ // where子单元分析
+ protected function parseWhereItem(Query $query, $field, $val): array
+ {
+ $key = $field ? $this->parseKey($query, $field) : '';
+ // 查询规则和条件
+ if (!is_array($val)) {
+ $val = ['=', $val];
+ }
+ [$exp, $value] = $val;
+
+ // 对一个字段使用多个查询条件
+ if (is_array($exp)) {
+ $data = [];
+ foreach ($val as $value) {
+ $exp = $value[0];
+ $value = $value[1];
+ if (!in_array($exp, $this->exp)) {
+ $exp = strtolower($exp);
+ if (isset($this->exp[$exp])) {
+ $exp = $this->exp[$exp];
+ }
+ }
+ $k = '$' . $exp;
+ $data[$k] = $value;
+ }
+ $result[$key] = $data;
+ return $result;
+ } elseif (!in_array($exp, $this->exp)) {
+ $exp = strtolower($exp);
+ if (isset($this->exp[$exp])) {
+ $exp = $this->exp[$exp];
+ } else {
+ throw new Exception('where express error:' . $exp);
+ }
+ }
+
+ $result = [];
+ if ('=' == $exp) {
+ // 普通查询
+ $result[$key] = $this->parseValue($query, $value, $key);
+ } elseif (in_array($exp, ['neq', 'ne', 'gt', 'egt', 'gte', 'lt', 'lte', 'elt', 'mod'])) {
+ // 比较运算
+ $k = '$' . $exp;
+ $result[$key] = [$k => $this->parseValue($query, $value, $key)];
+ } elseif ('null' == $exp) {
+ // NULL 查询
+ $result[$key] = null;
+ } elseif ('not null' == $exp) {
+ $result[$key] = ['$ne' => null];
+ } elseif ('all' == $exp) {
+ // 满足所有指定条件
+ $result[$key] = ['$all', $this->parseValue($query, $value, $key)];
+ } elseif ('between' == $exp) {
+ // 区间查询
+ $value = is_array($value) ? $value : explode(',', $value);
+ $result[$key] = ['$gte' => $this->parseValue($query, $value[0], $key), '$lte' => $this->parseValue($query, $value[1], $key)];
+ } elseif ('not between' == $exp) {
+ // 范围查询
+ $value = is_array($value) ? $value : explode(',', $value);
+ $result[$key] = ['$lt' => $this->parseValue($query, $value[0], $key), '$gt' => $this->parseValue($query, $value[1], $key)];
+ } elseif ('exists' == $exp) {
+ // 字段是否存在
+ $result[$key] = ['$exists' => (bool) $value];
+ } elseif ('type' == $exp) {
+ // 类型查询
+ $result[$key] = ['$type' => intval($value)];
+ } elseif ('exp' == $exp) {
+ // 表达式查询
+ $result['$where'] = $value instanceof Javascript ? $value : new Javascript($value);
+ } elseif ('like' == $exp) {
+ // 模糊查询 采用正则方式
+ $result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i');
+ } elseif (in_array($exp, ['nin', 'in'])) {
+ // IN 查询
+ $value = is_array($value) ? $value : explode(',', $value);
+ foreach ($value as $k => $val) {
+ $value[$k] = $this->parseValue($query, $val, $key);
+ }
+ $result[$key] = ['$' . $exp => $value];
+ } elseif ('regex' == $exp) {
+ $result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i');
+ } elseif ('< time' == $exp) {
+ $result[$key] = ['$lt' => $this->parseDateTime($query, $value, $field)];
+ } elseif ('> time' == $exp) {
+ $result[$key] = ['$gt' => $this->parseDateTime($query, $value, $field)];
+ } elseif ('between time' == $exp) {
+ // 区间查询
+ $value = is_array($value) ? $value : explode(',', $value);
+ $result[$key] = ['$gte' => $this->parseDateTime($query, $value[0], $field), '$lte' => $this->parseDateTime($query, $value[1], $field)];
+ } elseif ('not between time' == $exp) {
+ // 范围查询
+ $value = is_array($value) ? $value : explode(',', $value);
+ $result[$key] = ['$lt' => $this->parseDateTime($query, $value[0], $field), '$gt' => $this->parseDateTime($query, $value[1], $field)];
+ } elseif ('near' == $exp) {
+ // 经纬度查询
+ $result[$key] = ['$near' => $this->parseValue($query, $value, $key)];
+ } elseif ('size' == $exp) {
+ // 元素长度查询
+ $result[$key] = ['$size' => intval($value)];
+ } else {
+ // 普通查询
+ $result[$key] = $this->parseValue($query, $value, $key);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 日期时间条件解析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $value
+ * @param string $key
+ * @return string
+ */
+ protected function parseDateTime(Query $query, $value, $key)
+ {
+ // 获取时间字段类型
+ $type = $query->getFieldType($key);
+
+ if ($type) {
+ if (is_string($value)) {
+ $value = strtotime($value) ?: $value;
+ }
+
+ if (is_int($value)) {
+ if (preg_match('/(datetime|timestamp)/is', $type)) {
+ // 日期及时间戳类型
+ $value = date('Y-m-d H:i:s', $value);
+ } elseif (preg_match('/(date)/is', $type)) {
+ // 日期及时间戳类型
+ $value = date('Y-m-d', $value);
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * 获取最后写入的ID 如果是insertAll方法的话 返回所有写入的ID
+ * @access public
+ * @return mixed
+ */
+ public function getLastInsID()
+ {
+ return $this->insertId;
+ }
+
+ /**
+ * 生成insert BulkWrite对象
+ * @access public
+ * @param Query $query 查询对象
+ * @return BulkWrite
+ */
+ public function insert(Query $query): BulkWrite
+ {
+ // 分析并处理数据
+ $options = $query->getOptions();
+
+ $data = $this->parseData($query, $options['data']);
+
+ $bulk = new BulkWrite;
+
+ if ($insertId = $bulk->insert($data)) {
+ $this->insertId = $insertId;
+ }
+
+ $this->log('insert', $data, $options);
+
+ return $bulk;
+ }
+
+ /**
+ * 生成insertall BulkWrite对象
+ * @access public
+ * @param Query $query 查询对象
+ * @param array $dataSet 数据集
+ * @return BulkWrite
+ */
+ public function insertAll(Query $query, array $dataSet): BulkWrite
+ {
+ $bulk = new BulkWrite;
+ $options = $query->getOptions();
+
+ $this->insertId = [];
+ foreach ($dataSet as $data) {
+ // 分析并处理数据
+ $data = $this->parseData($query, $data);
+ if ($insertId = $bulk->insert($data)) {
+ $this->insertId[] = $insertId;
+ }
+ }
+
+ $this->log('insert', $dataSet, $options);
+
+ return $bulk;
+ }
+
+ /**
+ * 生成update BulkWrite对象
+ * @access public
+ * @param Query $query 查询对象
+ * @return BulkWrite
+ */
+ public function update(Query $query): BulkWrite
+ {
+ $options = $query->getOptions();
+
+ $data = $this->parseSet($query, $options['data']);
+ $where = $this->parseWhere($query, $options['where']);
+
+ if (1 == $options['limit']) {
+ $updateOptions = ['multi' => false];
+ } else {
+ $updateOptions = ['multi' => true];
+ }
+
+ $bulk = new BulkWrite;
+
+ $bulk->update($where, $data, $updateOptions);
+
+ $this->log('update', $data, $where);
+
+ return $bulk;
+ }
+
+ /**
+ * 生成delete BulkWrite对象
+ * @access public
+ * @param Query $query 查询对象
+ * @return BulkWrite
+ */
+ public function delete(Query $query): BulkWrite
+ {
+ $options = $query->getOptions();
+ $where = $this->parseWhere($query, $options['where']);
+
+ $bulk = new BulkWrite;
+
+ if (1 == $options['limit']) {
+ $deleteOptions = ['limit' => 1];
+ } else {
+ $deleteOptions = ['limit' => 0];
+ }
+
+ $bulk->delete($where, $deleteOptions);
+
+ $this->log('remove', $where, $deleteOptions);
+
+ return $bulk;
+ }
+
+ /**
+ * 生成Mongo查询对象
+ * @access public
+ * @param Query $query 查询对象
+ * @param bool $one 是否仅获取一个记录
+ * @return MongoQuery
+ */
+ public function select(Query $query, bool $one = false): MongoQuery
+ {
+ $options = $query->getOptions();
+
+ $where = $this->parseWhere($query, $options['where']);
+
+ if ($one) {
+ $options['limit'] = 1;
+ }
+
+ $query = new MongoQuery($where, $options);
+
+ $this->log('find', $where, $options);
+
+ return $query;
+ }
+
+ /**
+ * 生成Count命令
+ * @access public
+ * @param Query $query 查询对象
+ * @return Command
+ */
+ public function count(Query $query): Command
+ {
+ $options = $query->getOptions();
+
+ $cmd['count'] = $options['table'];
+ $cmd['query'] = (object) $this->parseWhere($query, $options['where']);
+
+ foreach (['hint', 'limit', 'maxTimeMS', 'skip'] as $option) {
+ if (isset($options[$option])) {
+ $cmd[$option] = $options[$option];
+ }
+ }
+
+ $command = new Command($cmd);
+ $this->log('cmd', 'count', $cmd);
+
+ return $command;
+ }
+
+ /**
+ * 聚合查询命令
+ * @access public
+ * @param Query $query 查询对象
+ * @param array $extra 指令和字段
+ * @return Command
+ */
+ public function aggregate(Query $query, array $extra): Command
+ {
+ $options = $query->getOptions();
+ [$fun, $field] = $extra;
+
+ if ('id' == $field && $this->connection->getConfig('pk_convert_id')) {
+ $field = '_id';
+ }
+
+ $group = isset($options['group']) ? '$' . $options['group'] : null;
+
+ $pipeline = [
+ ['$match' => (object) $this->parseWhere($query, $options['where'])],
+ ['$group' => ['_id' => $group, 'aggregate' => ['$' . $fun => '$' . $field]]],
+ ];
+
+ $cmd = [
+ 'aggregate' => $options['table'],
+ 'allowDiskUse' => true,
+ 'pipeline' => $pipeline,
+ 'cursor' => new \stdClass,
+ ];
+
+ foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) {
+ if (isset($options[$option])) {
+ $cmd[$option] = $options[$option];
+ }
+ }
+
+ $command = new Command($cmd);
+
+ $this->log('aggregate', $cmd);
+
+ return $command;
+ }
+
+ /**
+ * 多聚合查询命令, 可以对多个字段进行 group by 操作
+ *
+ * @param Query $query 查询对象
+ * @param array $extra 指令和字段
+ * @return Command
+ */
+ public function multiAggregate(Query $query, $extra): Command
+ {
+ $options = $query->getOptions();
+
+ [$aggregate, $groupBy] = $extra;
+
+ $groups = ['_id' => []];
+
+ foreach ($groupBy as $field) {
+ $groups['_id'][$field] = '$' . $field;
+ }
+
+ foreach ($aggregate as $fun => $field) {
+ $groups[$field . '_' . $fun] = ['$' . $fun => '$' . $field];
+ }
+
+ $pipeline = [
+ ['$match' => (object) $this->parseWhere($query, $options['where'])],
+ ['$group' => $groups],
+ ];
+
+ $cmd = [
+ 'aggregate' => $options['table'],
+ 'allowDiskUse' => true,
+ 'pipeline' => $pipeline,
+ 'cursor' => new \stdClass,
+ ];
+
+ foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) {
+ if (isset($options[$option])) {
+ $cmd[$option] = $options[$option];
+ }
+ }
+
+ $command = new Command($cmd);
+ $this->log('group', $cmd);
+
+ return $command;
+ }
+
+ /**
+ * 生成distinct命令
+ * @access public
+ * @param Query $query 查询对象
+ * @param string $field 字段名
+ * @return Command
+ */
+ public function distinct(Query $query, $field): Command
+ {
+ $options = $query->getOptions();
+
+ $cmd = [
+ 'distinct' => $options['table'],
+ 'key' => $field,
+ ];
+
+ if (!empty($options['where'])) {
+ $cmd['query'] = (object) $this->parseWhere($query, $options['where']);
+ }
+
+ if (isset($options['maxTimeMS'])) {
+ $cmd['maxTimeMS'] = $options['maxTimeMS'];
+ }
+
+ $command = new Command($cmd);
+
+ $this->log('cmd', 'distinct', $cmd);
+
+ return $command;
+ }
+
+ /**
+ * 查询所有的collection
+ * @access public
+ * @return Command
+ */
+ public function listcollections(): Command
+ {
+ $cmd = ['listCollections' => 1];
+ $command = new Command($cmd);
+
+ $this->log('cmd', 'listCollections', $cmd);
+
+ return $command;
+ }
+
+ /**
+ * 查询数据表的状态信息
+ * @access public
+ * @param Query $query 查询对象
+ * @return Command
+ */
+ public function collStats(Query $query): Command
+ {
+ $options = $query->getOptions();
+
+ $cmd = ['collStats' => $options['table']];
+ $command = new Command($cmd);
+
+ $this->log('cmd', 'collStats', $cmd);
+
+ return $command;
+ }
+
+ protected function log($type, $data, $options = [])
+ {
+ $this->connection->mongoLog($type, $data, $options);
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/builder/Mysql.php b/vendor/topthink/think-orm/src/db/builder/Mysql.php
new file mode 100644
index 000000000..136b0dee0
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/builder/Mysql.php
@@ -0,0 +1,426 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\builder;
+
+use think\db\Builder;
+use think\db\exception\DbException as Exception;
+use think\db\Query;
+use think\db\Raw;
+
+/**
+ * mysql数据库驱动
+ */
+class Mysql extends Builder
+{
+ /**
+ * 查询表达式解析
+ * @var array
+ */
+ protected $parser = [
+ 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='],
+ 'parseLike' => ['LIKE', 'NOT LIKE'],
+ 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'],
+ 'parseIn' => ['NOT IN', 'IN'],
+ 'parseExp' => ['EXP'],
+ 'parseRegexp' => ['REGEXP', 'NOT REGEXP'],
+ 'parseNull' => ['NOT NULL', 'NULL'],
+ 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'],
+ 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'],
+ 'parseExists' => ['NOT EXISTS', 'EXISTS'],
+ 'parseColumn' => ['COLUMN'],
+ 'parseFindInSet' => ['FIND IN SET'],
+ ];
+
+ /**
+ * SELECT SQL表达式
+ * @var string
+ */
+ protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%PARTITION%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * INSERT SQL表达式
+ * @var string
+ */
+ protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% SET %SET% %DUPLICATE%%COMMENT%';
+
+ /**
+ * INSERT ALL SQL表达式
+ * @var string
+ */
+ protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% (%FIELD%) VALUES %DATA% %DUPLICATE%%COMMENT%';
+
+ /**
+ * UPDATE SQL表达式
+ * @var string
+ */
+ protected $updateSql = 'UPDATE%EXTRA% %TABLE%%PARTITION% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * DELETE SQL表达式
+ * @var string
+ */
+ protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%PARTITION%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * 生成查询SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @param bool $one 是否仅获取一个记录
+ * @return string
+ */
+ public function select(Query $query, bool $one = false): string
+ {
+ $options = $query->getOptions();
+
+ return str_replace(
+ ['%TABLE%', '%PARTITION%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'],
+ [
+ $this->parseTable($query, $options['table']),
+ $this->parsePartition($query, $options['partition']),
+ $this->parseDistinct($query, $options['distinct']),
+ $this->parseExtra($query, $options['extra']),
+ $this->parseField($query, $options['field'] ?? '*'),
+ $this->parseJoin($query, $options['join']),
+ $this->parseWhere($query, $options['where']),
+ $this->parseGroup($query, $options['group']),
+ $this->parseHaving($query, $options['having']),
+ $this->parseOrder($query, $options['order']),
+ $this->parseLimit($query, $one ? '1' : $options['limit']),
+ $this->parseUnion($query, $options['union']),
+ $this->parseLock($query, $options['lock']),
+ $this->parseComment($query, $options['comment']),
+ $this->parseForce($query, $options['force']),
+ ],
+ $this->selectSql);
+ }
+
+ /**
+ * 生成Insert SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @return string
+ */
+ public function insert(Query $query): string
+ {
+ $options = $query->getOptions();
+
+ // 分析并处理数据
+ $data = $this->parseData($query, $options['data']);
+ if (empty($data)) {
+ return '';
+ }
+
+ $set = [];
+ foreach ($data as $key => $val) {
+ $set[] = $key . ' = ' . $val;
+ }
+
+ return str_replace(
+ ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%SET%', '%DUPLICATE%', '%COMMENT%'],
+ [
+ !empty($options['replace']) ? 'REPLACE' : 'INSERT',
+ $this->parseExtra($query, $options['extra']),
+ $this->parseTable($query, $options['table']),
+ $this->parsePartition($query, $options['partition']),
+ implode(' , ', $set),
+ $this->parseDuplicate($query, $options['duplicate']),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->insertSql);
+ }
+
+ /**
+ * 生成insertall SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @param array $dataSet 数据集
+ * @param bool $replace 是否replace
+ * @return string
+ */
+ public function insertAll(Query $query, array $dataSet, bool $replace = false): string
+ {
+ $options = $query->getOptions();
+
+ // 获取绑定信息
+ $bind = $query->getFieldsBindType();
+
+ // 获取合法的字段
+ if (empty($options['field']) || '*' == $options['field']) {
+ $allowFields = array_keys($bind);
+ } else {
+ $allowFields = $options['field'];
+ }
+
+ $fields = [];
+ $values = [];
+
+ foreach ($dataSet as $data) {
+ $data = $this->parseData($query, $data, $allowFields, $bind);
+
+ $values[] = '( ' . implode(',', array_values($data)) . ' )';
+
+ if (!isset($insertFields)) {
+ $insertFields = array_keys($data);
+ }
+ }
+
+ foreach ($insertFields as $field) {
+ $fields[] = $this->parseKey($query, $field);
+ }
+
+ return str_replace(
+ ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%FIELD%', '%DATA%', '%DUPLICATE%', '%COMMENT%'],
+ [
+ $replace ? 'REPLACE' : 'INSERT',
+ $this->parseExtra($query, $options['extra']),
+ $this->parseTable($query, $options['table']),
+ $this->parsePartition($query, $options['partition']),
+ implode(' , ', $fields),
+ implode(' , ', $values),
+ $this->parseDuplicate($query, $options['duplicate']),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->insertAllSql);
+ }
+
+ /**
+ * 生成update SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @return string
+ */
+ public function update(Query $query): string
+ {
+ $options = $query->getOptions();
+
+ $data = $this->parseData($query, $options['data']);
+
+ if (empty($data)) {
+ return '';
+ }
+ $set = [];
+ foreach ($data as $key => $val) {
+ $set[] = $key . ' = ' . $val;
+ }
+
+ return str_replace(
+ ['%TABLE%', '%PARTITION%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
+ [
+ $this->parseTable($query, $options['table']),
+ $this->parsePartition($query, $options['partition']),
+ $this->parseExtra($query, $options['extra']),
+ implode(' , ', $set),
+ $this->parseJoin($query, $options['join']),
+ $this->parseWhere($query, $options['where']),
+ $this->parseOrder($query, $options['order']),
+ $this->parseLimit($query, $options['limit']),
+ $this->parseLock($query, $options['lock']),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->updateSql);
+ }
+
+ /**
+ * 生成delete SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @return string
+ */
+ public function delete(Query $query): string
+ {
+ $options = $query->getOptions();
+
+ return str_replace(
+ ['%TABLE%', '%PARTITION%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
+ [
+ $this->parseTable($query, $options['table']),
+ $this->parsePartition($query, $options['partition']),
+ $this->parseExtra($query, $options['extra']),
+ !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '',
+ $this->parseJoin($query, $options['join']),
+ $this->parseWhere($query, $options['where']),
+ $this->parseOrder($query, $options['order']),
+ $this->parseLimit($query, $options['limit']),
+ $this->parseLock($query, $options['lock']),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->deleteSql);
+ }
+
+ /**
+ * 正则查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @return string
+ */
+ protected function parseRegexp(Query $query, string $key, string $exp, $value, string $field): string
+ {
+ if ($value instanceof Raw) {
+ $value = $this->parseRaw($query, $value);
+ }
+
+ return $key . ' ' . $exp . ' ' . $value;
+ }
+
+ /**
+ * FIND_IN_SET 查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @return string
+ */
+ protected function parseFindInSet(Query $query, string $key, string $exp, $value, string $field): string
+ {
+ if ($value instanceof Raw) {
+ $value = $this->parseRaw($query, $value);
+ }
+
+ return 'FIND_IN_SET(' . $value . ', ' . $key . ')';
+ }
+
+ /**
+ * 字段和表名处理
+ * @access public
+ * @param Query $query 查询对象
+ * @param mixed $key 字段名
+ * @param bool $strict 严格检测
+ * @return string
+ */
+ public function parseKey(Query $query, $key, bool $strict = false): string
+ {
+ if (is_int($key)) {
+ return (string) $key;
+ } elseif ($key instanceof Raw) {
+ return $this->parseRaw($query, $key);
+ }
+
+ $key = trim($key);
+
+ if (strpos($key, '->>') && false === strpos($key, '(')) {
+ // JSON字段支持
+ [$field, $name] = explode('->>', $key, 2);
+
+ return $this->parseKey($query, $field, true) . '->>\'$' . (strpos($name, '[') === 0 ? '' : '.') . str_replace('->>', '.', $name) . '\'';
+ } elseif (strpos($key, '->') && false === strpos($key, '(')) {
+ // JSON字段支持
+ [$field, $name] = explode('->', $key, 2);
+ return 'json_extract(' . $this->parseKey($query, $field, true) . ', \'$' . (strpos($name, '[') === 0 ? '' : '.') . str_replace('->', '.', $name) . '\')';
+ } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) {
+ [$table, $key] = explode('.', $key, 2);
+
+ $alias = $query->getOptions('alias');
+
+ if ('__TABLE__' == $table) {
+ $table = $query->getOptions('table');
+ $table = is_array($table) ? array_shift($table) : $table;
+ }
+
+ if (isset($alias[$table])) {
+ $table = $alias[$table];
+ }
+ }
+
+ if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) {
+ throw new Exception('not support data:' . $key);
+ }
+
+ if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) {
+ $key = '`' . $key . '`';
+ }
+
+ if (isset($table)) {
+ if (strpos($table, '.')) {
+ $table = str_replace('.', '`.`', $table);
+ }
+
+ $key = '`' . $table . '`.' . $key;
+ }
+
+ return $key;
+ }
+
+ /**
+ * 随机排序
+ * @access protected
+ * @param Query $query 查询对象
+ * @return string
+ */
+ protected function parseRand(Query $query): string
+ {
+ return 'rand()';
+ }
+
+ /**
+ * Partition 分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string|array $partition 分区
+ * @return string
+ */
+ protected function parsePartition(Query $query, $partition): string
+ {
+ if ('' == $partition) {
+ return '';
+ }
+
+ if (is_string($partition)) {
+ $partition = explode(',', $partition);
+ }
+
+ return ' PARTITION (' . implode(' , ', $partition) . ') ';
+ }
+
+ /**
+ * ON DUPLICATE KEY UPDATE 分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $duplicate
+ * @return string
+ */
+ protected function parseDuplicate(Query $query, $duplicate): string
+ {
+ if ('' == $duplicate) {
+ return '';
+ }
+
+ if ($duplicate instanceof Raw) {
+ return ' ON DUPLICATE KEY UPDATE ' . $this->parseRaw($query, $duplicate) . ' ';
+ }
+
+ if (is_string($duplicate)) {
+ $duplicate = explode(',', $duplicate);
+ }
+
+ $updates = [];
+ foreach ($duplicate as $key => $val) {
+ if (is_numeric($key)) {
+ $val = $this->parseKey($query, $val);
+ $updates[] = $val . ' = VALUES(' . $val . ')';
+ } elseif ($val instanceof Raw) {
+ $updates[] = $this->parseKey($query, $key) . " = " . $this->parseRaw($query, $val);
+ } else {
+ $name = $query->bindValue($val, $query->getConnection()->getFieldBindType($key));
+ $updates[] = $this->parseKey($query, $key) . " = :" . $name;
+ }
+ }
+
+ return ' ON DUPLICATE KEY UPDATE ' . implode(' , ', $updates) . ' ';
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/builder/Oracle.php b/vendor/topthink/think-orm/src/db/builder/Oracle.php
new file mode 100644
index 000000000..77ad3c81e
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/builder/Oracle.php
@@ -0,0 +1,95 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\builder;
+
+use think\db\Builder;
+use think\db\Query;
+
+/**
+ * Oracle数据库驱动
+ */
+class Oracle extends Builder
+{
+ protected $selectSql = 'SELECT * FROM (SELECT thinkphp.*, rownum AS numrow FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%) thinkphp ) %LIMIT%%COMMENT%';
+
+ /**
+ * limit分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $limit
+ * @return string
+ */
+ protected function parseLimit(Query $query, string $limit): string
+ {
+ $limitStr = '';
+
+ if (!empty($limit)) {
+ $limit = explode(',', $limit);
+
+ if (count($limit) > 1) {
+ $limitStr = "(numrow>" . $limit[0] . ") AND (numrow<=" . ($limit[0] + $limit[1]) . ")";
+ } else {
+ $limitStr = "(numrow>0 AND numrow<=" . $limit[0] . ")";
+ }
+
+ }
+
+ return $limitStr ? ' WHERE ' . $limitStr : '';
+ }
+
+ /**
+ * 设置锁机制
+ * @access protected
+ * @param Query $query 查询对象
+ * @param bool|false $lock
+ * @return string
+ */
+ protected function parseLock(Query $query, $lock = false): string
+ {
+ if (!$lock) {
+ return '';
+ }
+
+ return ' FOR UPDATE NOWAIT ';
+ }
+
+ /**
+ * 字段和表名处理
+ * @access public
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $strict
+ * @return string
+ */
+ public function parseKey(Query $query, $key, bool $strict = false): string
+ {
+ $key = trim($key);
+
+ if (strpos($key, '->') && false === strpos($key, '(')) {
+ // JSON字段支持
+ [$field, $name] = explode($key, '->');
+ $key = $field . '."' . $name . '"';
+ }
+
+ return $key;
+ }
+
+ /**
+ * 随机排序
+ * @access protected
+ * @param Query $query 查询对象
+ * @return string
+ */
+ protected function parseRand(Query $query): string
+ {
+ return 'DBMS_RANDOM.value';
+ }
+}
diff --git a/thinkphp/library/think/db/builder/Pgsql.php b/vendor/topthink/think-orm/src/db/builder/Pgsql.php
old mode 100755
new mode 100644
similarity index 70%
rename from thinkphp/library/think/db/builder/Pgsql.php
rename to vendor/topthink/think-orm/src/db/builder/Pgsql.php
index 742c7db37..4eace0ac9
--- a/thinkphp/library/think/db/builder/Pgsql.php
+++ b/vendor/topthink/think-orm/src/db/builder/Pgsql.php
@@ -2,25 +2,35 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\db\builder;
use think\db\Builder;
use think\db\Query;
+use think\db\Raw;
/**
* Pgsql数据库驱动
*/
class Pgsql extends Builder
{
+ /**
+ * INSERT SQL表达式
+ * @var string
+ */
+ protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
- protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
+ /**
+ * INSERT ALL SQL表达式
+ * @var string
+ */
protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%';
/**
@@ -30,7 +40,7 @@ class Pgsql extends Builder
* @param mixed $limit
* @return string
*/
- public function parseLimit(Query $query, $limit)
+ public function parseLimit(Query $query, string $limit): string
{
$limitStr = '';
@@ -54,22 +64,22 @@ class Pgsql extends Builder
* @param bool $strict 严格检测
* @return string
*/
- public function parseKey(Query $query, $key, $strict = false)
+ public function parseKey(Query $query, $key, bool $strict = false): string
{
- if (is_numeric($key)) {
- return $key;
- } elseif ($key instanceof Expression) {
- return $key->getValue();
+ if (is_int($key)) {
+ return (string) $key;
+ } elseif ($key instanceof Raw) {
+ return $this->parseRaw($query, $key);
}
$key = trim($key);
if (strpos($key, '->') && false === strpos($key, '(')) {
// JSON字段支持
- list($field, $name) = explode('->', $key);
- $key = $field . '->>\'' . $name . '\'';
+ [$field, $name] = explode('->', $key);
+ $key = '"' . $field . '"' . '->>\'' . $name . '\'';
} elseif (strpos($key, '.')) {
- list($table, $key) = explode('.', $key, 2);
+ [$table, $key] = explode('.', $key, 2);
$alias = $query->getOptions('alias');
@@ -81,6 +91,10 @@ class Pgsql extends Builder
if (isset($alias[$table])) {
$table = $alias[$table];
}
+
+ if ('*' != $key && !preg_match('/[,\"\*\(\).\s]/', $key)) {
+ $key = '"' . $key . '"';
+ }
}
if (isset($table)) {
@@ -96,7 +110,7 @@ class Pgsql extends Builder
* @param Query $query 查询对象
* @return string
*/
- protected function parseRand(Query $query)
+ protected function parseRand(Query $query): string
{
return 'RANDOM()';
}
diff --git a/thinkphp/library/think/db/builder/Sqlite.php b/vendor/topthink/think-orm/src/db/builder/Sqlite.php
old mode 100755
new mode 100644
similarity index 80%
rename from thinkphp/library/think/db/builder/Sqlite.php
rename to vendor/topthink/think-orm/src/db/builder/Sqlite.php
index 2b887ca8e..40cab7f85
--- a/thinkphp/library/think/db/builder/Sqlite.php
+++ b/vendor/topthink/think-orm/src/db/builder/Sqlite.php
@@ -2,24 +2,25 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\db\builder;
use think\db\Builder;
use think\db\Query;
+use think\db\Raw;
/**
* Sqlite数据库驱动
*/
class Sqlite extends Builder
{
-
/**
* limit
* @access public
@@ -27,7 +28,7 @@ class Sqlite extends Builder
* @param mixed $limit
* @return string
*/
- public function parseLimit(Query $query, $limit)
+ public function parseLimit(Query $query, string $limit): string
{
$limitStr = '';
@@ -49,7 +50,7 @@ class Sqlite extends Builder
* @param Query $query 查询对象
* @return string
*/
- protected function parseRand(Query $query)
+ protected function parseRand(Query $query): string
{
return 'RANDOM()';
}
@@ -62,18 +63,18 @@ class Sqlite extends Builder
* @param bool $strict 严格检测
* @return string
*/
- public function parseKey(Query $query, $key, $strict = false)
+ public function parseKey(Query $query, $key, bool $strict = false): string
{
- if (is_numeric($key)) {
- return $key;
- } elseif ($key instanceof Expression) {
- return $key->getValue();
+ if (is_int($key)) {
+ return (string) $key;
+ } elseif ($key instanceof Raw) {
+ return $this->parseRaw($query, $key);
}
$key = trim($key);
if (strpos($key, '.')) {
- list($table, $key) = explode('.', $key, 2);
+ [$table, $key] = explode('.', $key, 2);
$alias = $query->getOptions('alias');
diff --git a/thinkphp/library/think/db/builder/Sqlsrv.php b/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php
old mode 100755
new mode 100644
similarity index 60%
rename from thinkphp/library/think/db/builder/Sqlsrv.php
rename to vendor/topthink/think-orm/src/db/builder/Sqlsrv.php
index e24f7d25a..779b5e351
--- a/thinkphp/library/think/db/builder/Sqlsrv.php
+++ b/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php
@@ -12,21 +12,49 @@
namespace think\db\builder;
use think\db\Builder;
-use think\db\Expression;
+use think\db\exception\DbException as Exception;
use think\db\Query;
-use think\Exception;
+use think\db\Raw;
/**
* Sqlsrv数据库驱动
*/
class Sqlsrv extends Builder
{
- protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%';
+ /**
+ * SELECT SQL表达式
+ * @var string
+ */
+ protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%';
+ /**
+ * SELECT INSERT SQL表达式
+ * @var string
+ */
protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%';
- protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%';
- protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%';
- protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
- protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%';
+
+ /**
+ * UPDATE SQL表达式
+ * @var string
+ */
+ protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * DELETE SQL表达式
+ * @var string
+ */
+ protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * INSERT SQL表达式
+ * @var string
+ */
+ protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
+
+ /**
+ * INSERT ALL SQL表达式
+ * @var string
+ */
+ protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%';
/**
* order分析
@@ -35,35 +63,32 @@ class Sqlsrv extends Builder
* @param mixed $order
* @return string
*/
- protected function parseOrder(Query $query, $order)
+ protected function parseOrder(Query $query, array $order): string
{
if (empty($order)) {
return ' ORDER BY rand()';
}
+ $array = [];
+
foreach ($order as $key => $val) {
- if ($val instanceof Expression) {
- $array[] = $val->getValue();
+ if ($val instanceof Raw) {
+ $array[] = $this->parseRaw($query, $val);
} elseif ('[rand]' == $val) {
$array[] = $this->parseRand($query);
} else {
if (is_numeric($key)) {
- list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' ');
+ [$key, $sort] = explode(' ', strpos($val, ' ') ? $val : $val . ' ');
} else {
$sort = $val;
}
- if (preg_match('/^[\w\.]+$/', $key)) {
- $sort = strtoupper($sort);
- $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : '';
- $array[] = $this->parseKey($query, $key, true) . $sort;
- } else {
- throw new Exception('order express error:' . $key);
- }
+ $sort = in_array(strtolower($sort), ['asc', 'desc'], true) ? ' ' . $sort : '';
+ $array[] = $this->parseKey($query, $key, true) . $sort;
}
}
- return empty($array) ? '' : ' ORDER BY ' . implode(',', $array);
+ return ' ORDER BY ' . implode(',', $array);
}
/**
@@ -72,7 +97,7 @@ class Sqlsrv extends Builder
* @param Query $query 查询对象
* @return string
*/
- protected function parseRand(Query $query)
+ protected function parseRand(Query $query): string
{
return 'rand()';
}
@@ -85,18 +110,18 @@ class Sqlsrv extends Builder
* @param bool $strict 严格检测
* @return string
*/
- public function parseKey(Query $query, $key, $strict = false)
+ public function parseKey(Query $query, $key, bool $strict = false): string
{
- if (is_numeric($key)) {
- return $key;
- } elseif ($key instanceof Expression) {
- return $key->getValue();
+ if (is_int($key)) {
+ return (string) $key;
+ } elseif ($key instanceof Raw) {
+ return $this->parseRaw($query, $key);
}
$key = trim($key);
if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) {
- list($table, $key) = explode('.', $key, 2);
+ [$table, $key] = explode('.', $key, 2);
$alias = $query->getOptions('alias');
@@ -114,7 +139,7 @@ class Sqlsrv extends Builder
throw new Exception('not support data:' . $key);
}
- if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)\[.\s]/', $key))) {
+ if ('*' != $key && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) {
$key = '[' . $key . ']';
}
@@ -132,7 +157,7 @@ class Sqlsrv extends Builder
* @param mixed $limit
* @return string
*/
- protected function parseLimit(Query $query, $limit)
+ protected function parseLimit(Query $query, string $limit): string
{
if (empty($limit)) {
return '';
@@ -149,7 +174,7 @@ class Sqlsrv extends Builder
return 'WHERE ' . $limitStr;
}
- public function selectInsert(Query $query, $fields, $table)
+ public function selectInsert(Query $query, array $fields, string $table): string
{
$this->selectSql = $this->selectInsertSql;
diff --git a/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php b/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php
new file mode 100644
index 000000000..dabfb921a
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php
@@ -0,0 +1,107 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+use think\db\Raw;
+
+/**
+ * 聚合查询
+ */
+trait AggregateQuery
+{
+ /**
+ * 聚合查询
+ * @access protected
+ * @param string $aggregate 聚合方法
+ * @param string|Raw $field 字段名
+ * @param bool $force 强制转为数字类型
+ * @return mixed
+ */
+ protected function aggregate(string $aggregate, $field, bool $force = false)
+ {
+ return $this->connection->aggregate($this, $aggregate, $field, $force);
+ }
+
+ /**
+ * COUNT查询
+ * @access public
+ * @param string|Raw $field 字段名
+ * @return int
+ */
+ public function count(string $field = '*'): int
+ {
+ if (!empty($this->options['group'])) {
+ // 支持GROUP
+ $options = $this->getOptions();
+ $subSql = $this->options($options)
+ ->field('count(' . $field . ') AS think_count')
+ ->bind($this->bind)
+ ->buildSql();
+
+ $query = $this->newQuery()->table([$subSql => '_group_count_']);
+
+ $count = $query->aggregate('COUNT', '*');
+ } else {
+ $count = $this->aggregate('COUNT', $field);
+ }
+
+ return (int) $count;
+ }
+
+ /**
+ * SUM查询
+ * @access public
+ * @param string|Raw $field 字段名
+ * @return float
+ */
+ public function sum($field): float
+ {
+ return $this->aggregate('SUM', $field, true);
+ }
+
+ /**
+ * MIN查询
+ * @access public
+ * @param string|Raw $field 字段名
+ * @param bool $force 强制转为数字类型
+ * @return mixed
+ */
+ public function min($field, bool $force = true)
+ {
+ return $this->aggregate('MIN', $field, $force);
+ }
+
+ /**
+ * MAX查询
+ * @access public
+ * @param string|Raw $field 字段名
+ * @param bool $force 强制转为数字类型
+ * @return mixed
+ */
+ public function max($field, bool $force = true)
+ {
+ return $this->aggregate('MAX', $field, $force);
+ }
+
+ /**
+ * AVG查询
+ * @access public
+ * @param string|Raw $field 字段名
+ * @return float
+ */
+ public function avg($field): float
+ {
+ return $this->aggregate('AVG', $field, true);
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php b/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php
new file mode 100644
index 000000000..c33d1ed2f
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php
@@ -0,0 +1,229 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+use think\db\Raw;
+use think\helper\Str;
+
+/**
+ * JOIN和VIEW查询
+ */
+trait JoinAndViewQuery
+{
+
+ /**
+ * 查询SQL组装 join
+ * @access public
+ * @param mixed $join 关联的表名
+ * @param mixed $condition 条件
+ * @param string $type JOIN类型
+ * @param array $bind 参数绑定
+ * @return $this
+ */
+ public function join($join, string $condition = null, string $type = 'INNER', array $bind = [])
+ {
+ $table = $this->getJoinTable($join);
+
+ if (!empty($bind) && $condition) {
+ $this->bindParams($condition, $bind);
+ }
+
+ $this->options['join'][] = [$table, strtoupper($type), $condition];
+
+ return $this;
+ }
+
+ /**
+ * LEFT JOIN
+ * @access public
+ * @param mixed $join 关联的表名
+ * @param mixed $condition 条件
+ * @param array $bind 参数绑定
+ * @return $this
+ */
+ public function leftJoin($join, string $condition = null, array $bind = [])
+ {
+ return $this->join($join, $condition, 'LEFT', $bind);
+ }
+
+ /**
+ * RIGHT JOIN
+ * @access public
+ * @param mixed $join 关联的表名
+ * @param mixed $condition 条件
+ * @param array $bind 参数绑定
+ * @return $this
+ */
+ public function rightJoin($join, string $condition = null, array $bind = [])
+ {
+ return $this->join($join, $condition, 'RIGHT', $bind);
+ }
+
+ /**
+ * FULL JOIN
+ * @access public
+ * @param mixed $join 关联的表名
+ * @param mixed $condition 条件
+ * @param array $bind 参数绑定
+ * @return $this
+ */
+ public function fullJoin($join, string $condition = null, array $bind = [])
+ {
+ return $this->join($join, $condition, 'FULL');
+ }
+
+ /**
+ * 获取Join表名及别名 支持
+ * ['prefix_table或者子查询'=>'alias'] 'table alias'
+ * @access protected
+ * @param array|string|Raw $join JION表名
+ * @param string $alias 别名
+ * @return string|array
+ */
+ protected function getJoinTable($join, &$alias = null)
+ {
+ if (is_array($join)) {
+ $table = $join;
+ $alias = array_shift($join);
+ return $table;
+ } elseif ($join instanceof Raw) {
+ return $join;
+ }
+
+ $join = trim($join);
+
+ if (false !== strpos($join, '(')) {
+ // 使用子查询
+ $table = $join;
+ } else {
+ // 使用别名
+ if (strpos($join, ' ')) {
+ // 使用别名
+ [$table, $alias] = explode(' ', $join);
+ } else {
+ $table = $join;
+ if (false === strpos($join, '.')) {
+ $alias = $join;
+ }
+ }
+
+ if ($this->prefix && false === strpos($table, '.') && 0 !== strpos($table, $this->prefix)) {
+ $table = $this->getTable($table);
+ }
+ }
+
+ if (!empty($alias) && $table != $alias) {
+ $table = [$table => $alias];
+ }
+
+ return $table;
+ }
+
+ /**
+ * 指定JOIN查询字段
+ * @access public
+ * @param string|array $join 数据表
+ * @param string|array $field 查询字段
+ * @param string $on JOIN条件
+ * @param string $type JOIN类型
+ * @param array $bind 参数绑定
+ * @return $this
+ */
+ public function view($join, $field = true, $on = null, string $type = 'INNER', array $bind = [])
+ {
+ $this->options['view'] = true;
+
+ $fields = [];
+ $table = $this->getJoinTable($join, $alias);
+
+ if (true === $field) {
+ $fields = $alias . '.*';
+ } else {
+ if (is_string($field)) {
+ $field = explode(',', $field);
+ }
+
+ foreach ($field as $key => $val) {
+ if (is_numeric($key)) {
+ $fields[] = $alias . '.' . $val;
+
+ $this->options['map'][$val] = $alias . '.' . $val;
+ } else {
+ if (preg_match('/[,=\.\'\"\(\s]/', $key)) {
+ $name = $key;
+ } else {
+ $name = $alias . '.' . $key;
+ }
+
+ $fields[] = $name . ' AS ' . $val;
+
+ $this->options['map'][$val] = $name;
+ }
+ }
+ }
+
+ $this->field($fields);
+
+ if ($on) {
+ $this->join($table, $on, $type, $bind);
+ } else {
+ $this->table($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 视图查询处理
+ * @access protected
+ * @param array $options 查询参数
+ * @return void
+ */
+ protected function parseView(array &$options): void
+ {
+ foreach (['AND', 'OR'] as $logic) {
+ if (isset($options['where'][$logic])) {
+ foreach ($options['where'][$logic] as $key => $val) {
+ if (array_key_exists($key, $options['map'])) {
+ array_shift($val);
+ array_unshift($val, $options['map'][$key]);
+ $options['where'][$logic][$options['map'][$key]] = $val;
+ unset($options['where'][$logic][$key]);
+ }
+ }
+ }
+ }
+
+ if (isset($options['order'])) {
+ // 视图查询排序处理
+ foreach ($options['order'] as $key => $val) {
+ if (is_numeric($key) && is_string($val)) {
+ if (strpos($val, ' ')) {
+ [$field, $sort] = explode(' ', $val);
+ if (array_key_exists($field, $options['map'])) {
+ $options['order'][$options['map'][$field]] = $sort;
+ unset($options['order'][$key]);
+ }
+ } elseif (array_key_exists($val, $options['map'])) {
+ $options['order'][$options['map'][$val]] = 'asc';
+ unset($options['order'][$key]);
+ }
+ } elseif (array_key_exists($key, $options['map'])) {
+ $options['order'][$options['map'][$key]] = $val;
+ unset($options['order'][$key]);
+ }
+ }
+ }
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php b/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php
new file mode 100644
index 000000000..ffb72de48
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php
@@ -0,0 +1,524 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+use Closure;
+use think\helper\Str;
+use think\Model;
+use think\model\Collection as ModelCollection;
+
+/**
+ * 模型及关联查询
+ */
+trait ModelRelationQuery
+{
+
+ /**
+ * 当前模型对象
+ * @var Model
+ */
+ protected $model;
+
+ /**
+ * 指定模型
+ * @access public
+ * @param Model $model 模型对象实例
+ * @return $this
+ */
+ public function model(Model $model)
+ {
+ $this->model = $model;
+ return $this;
+ }
+
+ /**
+ * 获取当前的模型对象
+ * @access public
+ * @return Model|null
+ */
+ public function getModel()
+ {
+ return $this->model;
+ }
+
+ /**
+ * 设置需要隐藏的输出属性
+ * @access public
+ * @param array $hidden 需要隐藏的字段名
+ * @return $this
+ */
+ public function hidden(array $hidden)
+ {
+ $this->options['hidden'] = $hidden;
+ return $this;
+ }
+
+ /**
+ * 设置需要输出的属性
+ * @access public
+ * @param array $visible 需要输出的属性
+ * @return $this
+ */
+ public function visible(array $visible)
+ {
+ $this->options['visible'] = $visible;
+ return $this;
+ }
+
+ /**
+ * 设置需要追加输出的属性
+ * @access public
+ * @param array $append 需要追加的属性
+ * @return $this
+ */
+ public function append(array $append)
+ {
+ $this->options['append'] = $append;
+ return $this;
+ }
+
+ /**
+ * 添加查询范围
+ * @access public
+ * @param array|string|Closure $scope 查询范围定义
+ * @param array $args 参数
+ * @return $this
+ */
+ public function scope($scope, ...$args)
+ {
+ // 查询范围的第一个参数始终是当前查询对象
+ array_unshift($args, $this);
+
+ if ($scope instanceof Closure) {
+ call_user_func_array($scope, $args);
+ return $this;
+ }
+
+ if (is_string($scope)) {
+ $scope = explode(',', $scope);
+ }
+
+ if ($this->model) {
+ // 检查模型类的查询范围方法
+ foreach ($scope as $name) {
+ $method = 'scope' . trim($name);
+
+ if (method_exists($this->model, $method)) {
+ call_user_func_array([$this->model, $method], $args);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置关联查询
+ * @access public
+ * @param array $relation 关联名称
+ * @return $this
+ */
+ public function relation(array $relation)
+ {
+ if (!empty($relation)) {
+ $this->options['relation'] = $relation;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 使用搜索器条件搜索字段
+ * @access public
+ * @param string|array $fields 搜索字段
+ * @param mixed $data 搜索数据
+ * @param string $prefix 字段前缀标识
+ * @return $this
+ */
+ public function withSearch($fields, $data = [], string $prefix = '')
+ {
+ if (is_string($fields)) {
+ $fields = explode(',', $fields);
+ }
+
+ $likeFields = $this->getConfig('match_like_fields') ?: [];
+
+ foreach ($fields as $key => $field) {
+ if ($field instanceof Closure) {
+ $field($this, $data[$key] ?? null, $data, $prefix);
+ } elseif ($this->model) {
+ // 检测搜索器
+ $fieldName = is_numeric($key) ? $field : $key;
+ $method = 'search' . Str::studly($fieldName) . 'Attr';
+
+ if (method_exists($this->model, $method)) {
+ $this->model->$method($this, $data[$field] ?? null, $data, $prefix);
+ } elseif (isset($data[$field])) {
+ $this->where($fieldName, in_array($fieldName, $likeFields) ? 'like' : '=', in_array($fieldName, $likeFields) ? '%' . $data[$field] . '%' : $data[$field]);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置数据字段获取器
+ * @access public
+ * @param string|array $name 字段名
+ * @param callable $callback 闭包获取器
+ * @return $this
+ */
+ public function withAttr($name, callable $callback = null)
+ {
+ if (is_array($name)) {
+ $this->options['with_attr'] = $name;
+ } else {
+ $this->options['with_attr'][$name] = $callback;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 关联预载入 In方式
+ * @access public
+ * @param array|string $with 关联方法名称
+ * @return $this
+ */
+ public function with($with)
+ {
+ if (!empty($with)) {
+ $this->options['with'] = (array) $with;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 关联预载入 JOIN方式
+ * @access protected
+ * @param array|string $with 关联方法名
+ * @param string $joinType JOIN方式
+ * @return $this
+ */
+ public function withJoin($with, string $joinType = '')
+ {
+ if (empty($with)) {
+ return $this;
+ }
+
+ $with = (array) $with;
+ $first = true;
+
+ foreach ($with as $key => $relation) {
+ $closure = null;
+ $field = true;
+
+ if ($relation instanceof Closure) {
+ // 支持闭包查询过滤关联条件
+ $closure = $relation;
+ $relation = $key;
+ } elseif (is_array($relation)) {
+ $field = $relation;
+ $relation = $key;
+ } elseif (is_string($relation) && strpos($relation, '.')) {
+ $relation = strstr($relation, '.', true);
+ }
+
+ $result = $this->model->eagerly($this, $relation, $field, $joinType, $closure, $first);
+
+ if (!$result) {
+ unset($with[$key]);
+ } else {
+ $first = false;
+ }
+ }
+
+ $this->via();
+
+ $this->options['with_join'] = $with;
+
+ return $this;
+ }
+
+ /**
+ * 关联统计
+ * @access protected
+ * @param array|string $relations 关联方法名
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param bool $subQuery 是否使用子查询
+ * @return $this
+ */
+ protected function withAggregate($relations, string $aggregate = 'count', $field = '*', bool $subQuery = true)
+ {
+ if (!$subQuery) {
+ $this->options['with_count'][] = [$relations, $aggregate, $field];
+ } else {
+ if (!isset($this->options['field'])) {
+ $this->field('*');
+ }
+
+ $this->model->relationCount($this, (array) $relations, $aggregate, $field, true);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 关联缓存
+ * @access public
+ * @param string|array|bool $relation 关联方法名
+ * @param mixed $key 缓存key
+ * @param integer|\DateTime $expire 缓存有效期
+ * @param string $tag 缓存标签
+ * @return $this
+ */
+ public function withCache($relation = true, $key = true, $expire = null, string $tag = null)
+ {
+ if (false === $relation || false === $key || !$this->getConnection()->getCache()) {
+ return $this;
+ }
+
+ if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) {
+ $expire = $key;
+ $key = true;
+ }
+
+ if (true === $relation || is_numeric($relation)) {
+ $this->options['with_cache'] = $relation;
+ return $this;
+ }
+
+ $relations = (array) $relation;
+ foreach ($relations as $name => $relation) {
+ if (!is_numeric($name)) {
+ $this->options['with_cache'][$name] = is_array($relation) ? $relation : [$key, $relation, $tag];
+ } else {
+ $this->options['with_cache'][$relation] = [$key, $expire, $tag];
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 关联统计
+ * @access public
+ * @param string|array $relation 关联方法名
+ * @param bool $subQuery 是否使用子查询
+ * @return $this
+ */
+ public function withCount($relation, bool $subQuery = true)
+ {
+ return $this->withAggregate($relation, 'count', '*', $subQuery);
+ }
+
+ /**
+ * 关联统计Sum
+ * @access public
+ * @param string|array $relation 关联方法名
+ * @param string $field 字段
+ * @param bool $subQuery 是否使用子查询
+ * @return $this
+ */
+ public function withSum($relation, string $field, bool $subQuery = true)
+ {
+ return $this->withAggregate($relation, 'sum', $field, $subQuery);
+ }
+
+ /**
+ * 关联统计Max
+ * @access public
+ * @param string|array $relation 关联方法名
+ * @param string $field 字段
+ * @param bool $subQuery 是否使用子查询
+ * @return $this
+ */
+ public function withMax($relation, string $field, bool $subQuery = true)
+ {
+ return $this->withAggregate($relation, 'max', $field, $subQuery);
+ }
+
+ /**
+ * 关联统计Min
+ * @access public
+ * @param string|array $relation 关联方法名
+ * @param string $field 字段
+ * @param bool $subQuery 是否使用子查询
+ * @return $this
+ */
+ public function withMin($relation, string $field, bool $subQuery = true)
+ {
+ return $this->withAggregate($relation, 'min', $field, $subQuery);
+ }
+
+ /**
+ * 关联统计Avg
+ * @access public
+ * @param string|array $relation 关联方法名
+ * @param string $field 字段
+ * @param bool $subQuery 是否使用子查询
+ * @return $this
+ */
+ public function withAvg($relation, string $field, bool $subQuery = true)
+ {
+ return $this->withAggregate($relation, 'avg', $field, $subQuery);
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $relation 关联方法名
+ * @param mixed $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @return $this
+ */
+ public function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '')
+ {
+ return $this->model->has($relation, $operator, $count, $id, $joinType, $this);
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $relation 关联方法名
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @return $this
+ */
+ public function hasWhere(string $relation, $where = [], string $fields = '*', string $joinType = '')
+ {
+ return $this->model->hasWhere($relation, $where, $fields, $joinType, $this);
+ }
+
+ /**
+ * 查询数据转换为模型数据集对象
+ * @access protected
+ * @param array $resultSet 数据集
+ * @return ModelCollection
+ */
+ protected function resultSetToModelCollection(array $resultSet): ModelCollection
+ {
+ if (empty($resultSet)) {
+ return $this->model->toCollection();
+ }
+
+ // 检查动态获取器
+ if (!empty($this->options['with_attr'])) {
+ foreach ($this->options['with_attr'] as $name => $val) {
+ if (strpos($name, '.')) {
+ [$relation, $field] = explode('.', $name);
+
+ $withRelationAttr[$relation][$field] = $val;
+ unset($this->options['with_attr'][$name]);
+ }
+ }
+ }
+
+ $withRelationAttr = $withRelationAttr ?? [];
+
+ foreach ($resultSet as $key => &$result) {
+ // 数据转换为模型对象
+ $this->resultToModel($result, $this->options, true, $withRelationAttr);
+ }
+
+ if (!empty($this->options['with'])) {
+ // 预载入
+ $result->eagerlyResultSet($resultSet, $this->options['with'], $withRelationAttr, false, $this->options['with_cache'] ?? false);
+ }
+
+ if (!empty($this->options['with_join'])) {
+ // 预载入
+ $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true, $this->options['with_cache'] ?? false);
+ }
+
+ // 模型数据集转换
+ return $this->model->toCollection($resultSet);
+ }
+
+ /**
+ * 查询数据转换为模型对象
+ * @access protected
+ * @param array $result 查询数据
+ * @param array $options 查询参数
+ * @param bool $resultSet 是否为数据集查询
+ * @param array $withRelationAttr 关联字段获取器
+ * @return void
+ */
+ protected function resultToModel(array &$result, array $options = [], bool $resultSet = false, array $withRelationAttr = []): void
+ {
+ // 动态获取器
+ if (!empty($options['with_attr']) && empty($withRelationAttr)) {
+ foreach ($options['with_attr'] as $name => $val) {
+ if (strpos($name, '.')) {
+ [$relation, $field] = explode('.', $name);
+
+ $withRelationAttr[$relation][$field] = $val;
+ unset($options['with_attr'][$name]);
+ }
+ }
+ }
+
+ // JSON 数据处理
+ if (!empty($options['json'])) {
+ $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr);
+ }
+
+ $result = $this->model
+ ->newInstance($result, $resultSet ? null : $this->getModelUpdateCondition($options));
+
+ // 动态获取器
+ if (!empty($options['with_attr'])) {
+ $result->withAttribute($options['with_attr']);
+ }
+
+ // 输出属性控制
+ if (!empty($options['visible'])) {
+ $result->visible($options['visible']);
+ } elseif (!empty($options['hidden'])) {
+ $result->hidden($options['hidden']);
+ }
+
+ if (!empty($options['append'])) {
+ $result->append($options['append']);
+ }
+
+ // 关联查询
+ if (!empty($options['relation'])) {
+ $result->relationQuery($options['relation'], $withRelationAttr);
+ }
+
+ // 预载入查询
+ if (!$resultSet && !empty($options['with'])) {
+ $result->eagerlyResult($result, $options['with'], $withRelationAttr, false, $options['with_cache'] ?? false);
+ }
+
+ // JOIN预载入查询
+ if (!$resultSet && !empty($options['with_join'])) {
+ $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true, $options['with_cache'] ?? false);
+ }
+
+ // 关联统计
+ if (!empty($options['with_count'])) {
+ foreach ($options['with_count'] as $val) {
+ $result->relationCount($this, (array) $val[0], $val[1], $val[2], false);
+ }
+ }
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/ParamsBind.php b/vendor/topthink/think-orm/src/db/concern/ParamsBind.php
new file mode 100644
index 000000000..296e2212d
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/ParamsBind.php
@@ -0,0 +1,106 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+use PDO;
+
+/**
+ * 参数绑定支持
+ */
+trait ParamsBind
+{
+ /**
+ * 当前参数绑定
+ * @var array
+ */
+ protected $bind = [];
+
+ /**
+ * 批量参数绑定
+ * @access public
+ * @param array $value 绑定变量值
+ * @return $this
+ */
+ public function bind(array $value)
+ {
+ $this->bind = array_merge($this->bind, $value);
+ return $this;
+ }
+
+ /**
+ * 单个参数绑定
+ * @access public
+ * @param mixed $value 绑定变量值
+ * @param integer $type 绑定类型
+ * @param string $name 绑定标识
+ * @return string
+ */
+ public function bindValue($value, int $type = null, string $name = null)
+ {
+ $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_' . mt_rand() . '_';
+
+ $this->bind[$name] = [$value, $type ?: PDO::PARAM_STR];
+ return $name;
+ }
+
+ /**
+ * 检测参数是否已经绑定
+ * @access public
+ * @param string $key 参数名
+ * @return bool
+ */
+ public function isBind($key)
+ {
+ return isset($this->bind[$key]);
+ }
+
+ /**
+ * 参数绑定
+ * @access public
+ * @param string $sql 绑定的sql表达式
+ * @param array $bind 参数绑定
+ * @return void
+ */
+ public function bindParams(string &$sql, array $bind = []): void
+ {
+ foreach ($bind as $key => $value) {
+ if (is_array($value)) {
+ $name = $this->bindValue($value[0], $value[1], $value[2] ?? null);
+ } else {
+ $name = $this->bindValue($value);
+ }
+
+ if (is_numeric($key)) {
+ $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1);
+ } else {
+ $sql = str_replace(':' . $key, ':' . $name, $sql);
+ }
+ }
+ }
+
+ /**
+ * 获取绑定的参数 并清空
+ * @access public
+ * @param bool $clear 是否清空绑定数据
+ * @return array
+ */
+ public function getBind(bool $clear = true): array
+ {
+ $bind = $this->bind;
+ if ($clear) {
+ $this->bind = [];
+ }
+
+ return $bind;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/ResultOperation.php b/vendor/topthink/think-orm/src/db/concern/ResultOperation.php
new file mode 100644
index 000000000..d93c409a9
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/ResultOperation.php
@@ -0,0 +1,247 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+use Closure;
+use think\Collection;
+use think\db\exception\DataNotFoundException;
+use think\db\exception\DbException;
+use think\db\exception\ModelNotFoundException;
+use think\db\Query;
+use think\helper\Str;
+use think\Model;
+
+/**
+ * 查询数据处理
+ */
+trait ResultOperation
+{
+ /**
+ * 是否允许返回空数据(或空模型)
+ * @access public
+ * @param bool $allowEmpty 是否允许为空
+ * @return $this
+ */
+ public function allowEmpty(bool $allowEmpty = true)
+ {
+ $this->options['allow_empty'] = $allowEmpty;
+ return $this;
+ }
+
+ /**
+ * 设置查询数据不存在是否抛出异常
+ * @access public
+ * @param bool $fail 数据不存在是否抛出异常
+ * @return $this
+ */
+ public function failException(bool $fail = true)
+ {
+ $this->options['fail'] = $fail;
+ return $this;
+ }
+
+ /**
+ * 处理数据
+ * @access protected
+ * @param array $result 查询数据
+ * @return void
+ */
+ protected function result(array &$result): void
+ {
+ if (!empty($this->options['json'])) {
+ $this->jsonResult($result, $this->options['json'], true);
+ }
+
+ if (!empty($this->options['with_attr'])) {
+ $this->getResultAttr($result, $this->options['with_attr']);
+ }
+
+ $this->filterResult($result);
+ }
+
+ /**
+ * 处理数据集
+ * @access public
+ * @param array $resultSet 数据集
+ * @return void
+ */
+ protected function resultSet(array &$resultSet): void
+ {
+ if (!empty($this->options['json'])) {
+ foreach ($resultSet as &$result) {
+ $this->jsonResult($result, $this->options['json'], true);
+ }
+ }
+
+ if (!empty($this->options['with_attr'])) {
+ foreach ($resultSet as &$result) {
+ $this->getResultAttr($result, $this->options['with_attr']);
+ }
+ }
+
+ if (!empty($this->options['visible']) || !empty($this->options['hidden'])) {
+ foreach ($resultSet as &$result) {
+ $this->filterResult($result);
+ }
+ }
+
+ // 返回Collection对象
+ $resultSet = new Collection($resultSet);
+ }
+
+ /**
+ * 处理数据的可见和隐藏
+ * @access protected
+ * @param array $result 查询数据
+ * @return void
+ */
+ protected function filterResult(&$result): void
+ {
+ $array = [];
+ if (!empty($this->options['visible'])) {
+ foreach ($this->options['visible'] as $key) {
+ $array[] = $key;
+ }
+ $result = array_intersect_key($result, array_flip($array));
+ } elseif (!empty($this->options['hidden'])) {
+ foreach ($this->options['hidden'] as $key) {
+ $array[] = $key;
+ }
+ $result = array_diff_key($result, array_flip($array));
+ }
+ }
+
+ /**
+ * 使用获取器处理数据
+ * @access protected
+ * @param array $result 查询数据
+ * @param array $withAttr 字段获取器
+ * @return void
+ */
+ protected function getResultAttr(array &$result, array $withAttr = []): void
+ {
+ foreach ($withAttr as $name => $closure) {
+ $name = Str::snake($name);
+
+ if (strpos($name, '.')) {
+ // 支持JSON字段 获取器定义
+ [$key, $field] = explode('.', $name);
+
+ if (isset($result[$key])) {
+ $result[$key][$field] = $closure($result[$key][$field] ?? null, $result[$key]);
+ }
+ } else {
+ $result[$name] = $closure($result[$name] ?? null, $result);
+ }
+ }
+ }
+
+ /**
+ * 处理空数据
+ * @access protected
+ * @return array|Model|null
+ * @throws DbException
+ * @throws ModelNotFoundException
+ * @throws DataNotFoundException
+ */
+ protected function resultToEmpty()
+ {
+ if (!empty($this->options['fail'])) {
+ $this->throwNotFound();
+ } elseif (!empty($this->options['allow_empty'])) {
+ return !empty($this->model) ? $this->model->newInstance() : [];
+ }
+ }
+
+ /**
+ * 查找单条记录 不存在返回空数据(或者空模型)
+ * @access public
+ * @param mixed $data 数据
+ * @return array|Model
+ */
+ public function findOrEmpty($data = null)
+ {
+ return $this->allowEmpty(true)->find($data);
+ }
+
+ /**
+ * JSON字段数据转换
+ * @access protected
+ * @param array $result 查询数据
+ * @param array $json JSON字段
+ * @param bool $assoc 是否转换为数组
+ * @param array $withRelationAttr 关联获取器
+ * @return void
+ */
+ protected function jsonResult(array &$result, array $json = [], bool $assoc = false, array $withRelationAttr = []): void
+ {
+ foreach ($json as $name) {
+ if (!isset($result[$name])) {
+ continue;
+ }
+
+ $result[$name] = json_decode($result[$name], true);
+
+ if (isset($withRelationAttr[$name])) {
+ foreach ($withRelationAttr[$name] as $key => $closure) {
+ $result[$name][$key] = $closure($result[$name][$key] ?? null, $result[$name]);
+ }
+ }
+
+ if (!$assoc) {
+ $result[$name] = (object) $result[$name];
+ }
+ }
+ }
+
+ /**
+ * 查询失败 抛出异常
+ * @access protected
+ * @return void
+ * @throws ModelNotFoundException
+ * @throws DataNotFoundException
+ */
+ protected function throwNotFound(): void
+ {
+ if (!empty($this->model)) {
+ $class = get_class($this->model);
+ throw new ModelNotFoundException('model data Not Found:' . $class, $class, $this->options);
+ }
+
+ $table = $this->getTable();
+ throw new DataNotFoundException('table data not Found:' . $table, $table, $this->options);
+ }
+
+ /**
+ * 查找多条记录 如果不存在则抛出异常
+ * @access public
+ * @param array|string|Query|Closure $data 数据
+ * @return array|Model
+ */
+ public function selectOrFail($data = null)
+ {
+ return $this->failException(true)->select($data);
+ }
+
+ /**
+ * 查找单条记录 如果不存在则抛出异常
+ * @access public
+ * @param array|string|Query|Closure $data 数据
+ * @return array|Model
+ */
+ public function findOrFail($data = null)
+ {
+ return $this->failException(true)->find($data);
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php b/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php
new file mode 100644
index 000000000..9070befea
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php
@@ -0,0 +1,99 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+/**
+ * 数据字段信息
+ */
+trait TableFieldInfo
+{
+
+ /**
+ * 获取数据表字段信息
+ * @access public
+ * @param string $tableName 数据表名
+ * @return array
+ */
+ public function getTableFields($tableName = ''): array
+ {
+ if ('' == $tableName) {
+ $tableName = $this->getTable();
+ }
+
+ return $this->connection->getTableFields($tableName);
+ }
+
+ /**
+ * 获取详细字段类型信息
+ * @access public
+ * @param string $tableName 数据表名称
+ * @return array
+ */
+ public function getFields(string $tableName = ''): array
+ {
+ return $this->connection->getFields($tableName ?: $this->getTable());
+ }
+
+ /**
+ * 获取字段类型信息
+ * @access public
+ * @return array
+ */
+ public function getFieldsType(): array
+ {
+ if (!empty($this->options['field_type'])) {
+ return $this->options['field_type'];
+ }
+
+ return $this->connection->getFieldsType($this->getTable());
+ }
+
+ /**
+ * 获取字段类型信息
+ * @access public
+ * @param string $field 字段名
+ * @return string|null
+ */
+ public function getFieldType(string $field)
+ {
+ $fieldType = $this->getFieldsType();
+
+ return $fieldType[$field] ?? null;
+ }
+
+ /**
+ * 获取字段类型信息
+ * @access public
+ * @return array
+ */
+ public function getFieldsBindType(): array
+ {
+ $fieldType = $this->getFieldsType();
+
+ return array_map([$this->connection, 'getFieldBindType'], $fieldType);
+ }
+
+ /**
+ * 获取字段类型信息
+ * @access public
+ * @param string $field 字段名
+ * @return int
+ */
+ public function getFieldBindType(string $field): int
+ {
+ $fieldType = $this->getFieldType($field);
+
+ return $this->connection->getFieldBindType($fieldType ?: '');
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php b/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php
new file mode 100644
index 000000000..1267e5401
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php
@@ -0,0 +1,214 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+/**
+ * 时间查询支持
+ */
+trait TimeFieldQuery
+{
+ /**
+ * 日期查询表达式
+ * @var array
+ */
+ protected $timeRule = [
+ 'today' => ['today', 'tomorrow -1second'],
+ 'yesterday' => ['yesterday', 'today -1second'],
+ 'week' => ['this week 00:00:00', 'next week 00:00:00 -1second'],
+ 'last week' => ['last week 00:00:00', 'this week 00:00:00 -1second'],
+ 'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00 -1second'],
+ 'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00 -1second'],
+ 'year' => ['this year 1/1', 'next year 1/1 -1second'],
+ 'last year' => ['last year 1/1', 'this year 1/1 -1second'],
+ ];
+
+ /**
+ * 添加日期或者时间查询规则
+ * @access public
+ * @param array $rule 时间表达式
+ * @return $this
+ */
+ public function timeRule(array $rule)
+ {
+ $this->timeRule = array_merge($this->timeRule, $rule);
+ return $this;
+ }
+
+ /**
+ * 查询日期或者时间
+ * @access public
+ * @param string $field 日期字段名
+ * @param string $op 比较运算符或者表达式
+ * @param string|array $range 比较范围
+ * @param string $logic AND OR
+ * @return $this
+ */
+ public function whereTime(string $field, string $op, $range = null, string $logic = 'AND')
+ {
+ if (is_null($range)) {
+ if (isset($this->timeRule[$op])) {
+ $range = $this->timeRule[$op];
+ } else {
+ $range = $op;
+ }
+ $op = is_array($range) ? 'between' : '>=';
+ }
+
+ return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true);
+ }
+
+ /**
+ * 查询某个时间间隔数据
+ * @access public
+ * @param string $field 日期字段名
+ * @param string $start 开始时间
+ * @param string $interval 时间间隔单位 day/month/year/week/hour/minute/second
+ * @param int $step 间隔
+ * @param string $logic AND OR
+ * @return $this
+ */
+ public function whereTimeInterval(string $field, string $start, string $interval = 'day', int $step = 1, string $logic = 'AND')
+ {
+ $startTime = strtotime($start);
+ $endTime = strtotime(($step > 0 ? '+' : '-') . abs($step) . ' ' . $interval . (abs($step) > 1 ? 's' : ''), $startTime);
+
+ return $this->whereTime($field, 'between', $step > 0 ? [$startTime, $endTime - 1] : [$endTime, $startTime - 1], $logic);
+ }
+
+ /**
+ * 查询月数据 whereMonth('time_field', '2018-1')
+ * @access public
+ * @param string $field 日期字段名
+ * @param string $month 月份信息
+ * @param int $step 间隔
+ * @param string $logic AND OR
+ * @return $this
+ */
+ public function whereMonth(string $field, string $month = 'this month', int $step = 1, string $logic = 'AND')
+ {
+ if (in_array($month, ['this month', 'last month'])) {
+ $month = date('Y-m', strtotime($month));
+ }
+
+ return $this->whereTimeInterval($field, $month, 'month', $step, $logic);
+ }
+
+ /**
+ * 查询周数据 whereWeek('time_field', '2018-1-1') 从2018-1-1开始的一周数据
+ * @access public
+ * @param string $field 日期字段名
+ * @param string $week 周信息
+ * @param int $step 间隔
+ * @param string $logic AND OR
+ * @return $this
+ */
+ public function whereWeek(string $field, string $week = 'this week', int $step = 1, string $logic = 'AND')
+ {
+ if (in_array($week, ['this week', 'last week'])) {
+ $week = date('Y-m-d', strtotime($week));
+ }
+
+ return $this->whereTimeInterval($field, $week, 'week', $step, $logic);
+ }
+
+ /**
+ * 查询年数据 whereYear('time_field', '2018')
+ * @access public
+ * @param string $field 日期字段名
+ * @param string $year 年份信息
+ * @param int $step 间隔
+ * @param string $logic AND OR
+ * @return $this
+ */
+ public function whereYear(string $field, string $year = 'this year', int $step = 1, string $logic = 'AND')
+ {
+ if (in_array($year, ['this year', 'last year'])) {
+ $year = date('Y', strtotime($year));
+ }
+
+ return $this->whereTimeInterval($field, $year . '-1-1', 'year', $step, $logic);
+ }
+
+ /**
+ * 查询日数据 whereDay('time_field', '2018-1-1')
+ * @access public
+ * @param string $field 日期字段名
+ * @param string $day 日期信息
+ * @param int $step 间隔
+ * @param string $logic AND OR
+ * @return $this
+ */
+ public function whereDay(string $field, string $day = 'today', int $step = 1, string $logic = 'AND')
+ {
+ if (in_array($day, ['today', 'yesterday'])) {
+ $day = date('Y-m-d', strtotime($day));
+ }
+
+ return $this->whereTimeInterval($field, $day, 'day', $step, $logic);
+ }
+
+ /**
+ * 查询日期或者时间范围 whereBetweenTime('time_field', '2018-1-1','2018-1-15')
+ * @access public
+ * @param string $field 日期字段名
+ * @param string|int $startTime 开始时间
+ * @param string|int $endTime 结束时间
+ * @param string $logic AND OR
+ * @return $this
+ */
+ public function whereBetweenTime(string $field, $startTime, $endTime, string $logic = 'AND')
+ {
+ return $this->whereTime($field, 'between', [$startTime, $endTime], $logic);
+ }
+
+ /**
+ * 查询日期或者时间范围 whereNotBetweenTime('time_field', '2018-1-1','2018-1-15')
+ * @access public
+ * @param string $field 日期字段名
+ * @param string|int $startTime 开始时间
+ * @param string|int $endTime 结束时间
+ * @return $this
+ */
+ public function whereNotBetweenTime(string $field, $startTime, $endTime)
+ {
+ return $this->whereTime($field, '<', $startTime)
+ ->whereTime($field, '>', $endTime);
+ }
+
+ /**
+ * 查询当前时间在两个时间字段范围 whereBetweenTimeField('start_time', 'end_time')
+ * @access public
+ * @param string $startField 开始时间字段
+ * @param string $endField 结束时间字段
+ * @return $this
+ */
+ public function whereBetweenTimeField(string $startField, string $endField)
+ {
+ return $this->whereTime($startField, '<=', time())
+ ->whereTime($endField, '>=', time());
+ }
+
+ /**
+ * 查询当前时间不在两个时间字段范围 whereNotBetweenTimeField('start_time', 'end_time')
+ * @access public
+ * @param string $startField 开始时间字段
+ * @param string $endField 结束时间字段
+ * @return $this
+ */
+ public function whereNotBetweenTimeField(string $startField, string $endField)
+ {
+ return $this->whereTime($startField, '>', time())
+ ->whereTime($endField, '<', time(), 'OR');
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/Transaction.php b/vendor/topthink/think-orm/src/db/concern/Transaction.php
new file mode 100644
index 000000000..f804ae2d3
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/Transaction.php
@@ -0,0 +1,117 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+use think\db\BaseQuery;
+
+/**
+ * 事务支持
+ */
+trait Transaction
+{
+
+ /**
+ * 执行数据库Xa事务
+ * @access public
+ * @param callable $callback 数据操作方法回调
+ * @param array $dbs 多个查询对象或者连接对象
+ * @return mixed
+ * @throws PDOException
+ * @throws \Exception
+ * @throws \Throwable
+ */
+ public function transactionXa($callback, array $dbs = [])
+ {
+ $xid = uniqid('xa');
+
+ if (empty($dbs)) {
+ $dbs[] = $this->getConnection();
+ }
+
+ foreach ($dbs as $key => $db) {
+ if ($db instanceof BaseQuery) {
+ $db = $db->getConnection();
+
+ $dbs[$key] = $db;
+ }
+
+ $db->startTransXa($xid);
+ }
+
+ try {
+ $result = null;
+ if (is_callable($callback)) {
+ $result = call_user_func_array($callback, [$this]);
+ }
+
+ foreach ($dbs as $db) {
+ $db->prepareXa($xid);
+ }
+
+ foreach ($dbs as $db) {
+ $db->commitXa($xid);
+ }
+
+ return $result;
+ } catch (\Exception | \Throwable $e) {
+ foreach ($dbs as $db) {
+ $db->rollbackXa($xid);
+ }
+ throw $e;
+ }
+ }
+
+ /**
+ * 执行数据库事务
+ * @access public
+ * @param callable $callback 数据操作方法回调
+ * @return mixed
+ */
+ public function transaction(callable $callback)
+ {
+ return $this->connection->transaction($callback);
+ }
+
+ /**
+ * 启动事务
+ * @access public
+ * @return void
+ */
+ public function startTrans(): void
+ {
+ $this->connection->startTrans();
+ }
+
+ /**
+ * 用于非自动提交状态下面的查询提交
+ * @access public
+ * @return void
+ * @throws PDOException
+ */
+ public function commit(): void
+ {
+ $this->connection->commit();
+ }
+
+ /**
+ * 事务回滚
+ * @access public
+ * @return void
+ * @throws PDOException
+ */
+ public function rollback(): void
+ {
+ $this->connection->rollback();
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/WhereQuery.php b/vendor/topthink/think-orm/src/db/concern/WhereQuery.php
new file mode 100644
index 000000000..131162887
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/WhereQuery.php
@@ -0,0 +1,532 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+use Closure;
+use think\db\BaseQuery;
+use think\db\Raw;
+
+trait WhereQuery
+{
+ /**
+ * 指定AND查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $op 查询表达式
+ * @param mixed $condition 查询条件
+ * @return $this
+ */
+ public function where($field, $op = null, $condition = null)
+ {
+ if ($field instanceof $this) {
+ $this->parseQueryWhere($field);
+ return $this;
+ } elseif (true === $field || 1 === $field) {
+ $this->options['where']['AND'][] = true;
+ return $this;
+ }
+
+ $param = func_get_args();
+ array_shift($param);
+ return $this->parseWhereExp('AND', $field, $op, $condition, $param);
+ }
+
+ /**
+ * 解析Query对象查询条件
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return void
+ */
+ protected function parseQueryWhere(BaseQuery $query): void
+ {
+ $this->options['where'] = $query->getOptions('where');
+
+ if ($query->getOptions('via')) {
+ $via = $query->getOptions('via');
+ foreach ($this->options['where'] as $logic => &$where) {
+ foreach ($where as $key => &$val) {
+ if (is_array($val) && !strpos($val[0], '.')) {
+ $val[0] = $via . '.' . $val[0];
+ }
+ }
+ }
+ }
+
+ $this->bind($query->getBind(false));
+ }
+
+ /**
+ * 指定OR查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $op 查询表达式
+ * @param mixed $condition 查询条件
+ * @return $this
+ */
+ public function whereOr($field, $op = null, $condition = null)
+ {
+ $param = func_get_args();
+ array_shift($param);
+ return $this->parseWhereExp('OR', $field, $op, $condition, $param);
+ }
+
+ /**
+ * 指定XOR查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $op 查询表达式
+ * @param mixed $condition 查询条件
+ * @return $this
+ */
+ public function whereXor($field, $op = null, $condition = null)
+ {
+ $param = func_get_args();
+ array_shift($param);
+ return $this->parseWhereExp('XOR', $field, $op, $condition, $param);
+ }
+
+ /**
+ * 指定Null查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereNull(string $field, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'NULL', null, [], true);
+ }
+
+ /**
+ * 指定NotNull查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereNotNull(string $field, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true);
+ }
+
+ /**
+ * 指定Exists查询条件
+ * @access public
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereExists($condition, string $logic = 'AND')
+ {
+ if (is_string($condition)) {
+ $condition = new Raw($condition);
+ }
+
+ $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition];
+ return $this;
+ }
+
+ /**
+ * 指定NotExists查询条件
+ * @access public
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereNotExists($condition, string $logic = 'AND')
+ {
+ if (is_string($condition)) {
+ $condition = new Raw($condition);
+ }
+
+ $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition];
+ return $this;
+ }
+
+ /**
+ * 指定In查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereIn(string $field, $condition, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true);
+ }
+
+ /**
+ * 指定NotIn查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereNotIn(string $field, $condition, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true);
+ }
+
+ /**
+ * 指定Like查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereLike(string $field, $condition, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true);
+ }
+
+ /**
+ * 指定NotLike查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereNotLike(string $field, $condition, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true);
+ }
+
+ /**
+ * 指定Between查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereBetween(string $field, $condition, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true);
+ }
+
+ /**
+ * 指定NotBetween查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereNotBetween(string $field, $condition, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true);
+ }
+
+ /**
+ * 指定FIND_IN_SET查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereFindInSet(string $field, $condition, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'FIND IN SET', $condition, [], true);
+ }
+
+ /**
+ * 比较两个字段
+ * @access public
+ * @param string $field1 查询字段
+ * @param string $operator 比较操作符
+ * @param string $field2 比较字段
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereColumn(string $field1, string $operator, string $field2 = null, string $logic = 'AND')
+ {
+ if (is_null($field2)) {
+ $field2 = $operator;
+ $operator = '=';
+ }
+
+ return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true);
+ }
+
+ /**
+ * 设置软删除字段及条件
+ * @access public
+ * @param string $field 查询字段
+ * @param mixed $condition 查询条件
+ * @return $this
+ */
+ public function useSoftDelete(string $field, $condition = null)
+ {
+ if ($field) {
+ $this->options['soft_delete'] = [$field, $condition];
+ }
+
+ return $this;
+ }
+
+ /**
+ * 指定Exp查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param string $where 查询条件
+ * @param array $bind 参数绑定
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereExp(string $field, string $where, array $bind = [], string $logic = 'AND')
+ {
+ $this->options['where'][$logic][] = [$field, 'EXP', new Raw($where, $bind)];
+
+ return $this;
+ }
+
+ /**
+ * 指定字段Raw查询
+ * @access public
+ * @param string $field 查询字段表达式
+ * @param mixed $op 查询表达式
+ * @param string $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereFieldRaw(string $field, $op, $condition = null, string $logic = 'AND')
+ {
+ if (is_null($condition)) {
+ $condition = $op;
+ $op = '=';
+ }
+
+ $this->options['where'][$logic][] = [new Raw($field), $op, $condition];
+ return $this;
+ }
+
+ /**
+ * 指定表达式查询条件
+ * @access public
+ * @param string $where 查询条件
+ * @param array $bind 参数绑定
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereRaw(string $where, array $bind = [], string $logic = 'AND')
+ {
+ $this->options['where'][$logic][] = new Raw($where, $bind);
+
+ return $this;
+ }
+
+ /**
+ * 指定表达式查询条件 OR
+ * @access public
+ * @param string $where 查询条件
+ * @param array $bind 参数绑定
+ * @return $this
+ */
+ public function whereOrRaw(string $where, array $bind = [])
+ {
+ return $this->whereRaw($where, $bind, 'OR');
+ }
+
+ /**
+ * 分析查询表达式
+ * @access protected
+ * @param string $logic 查询逻辑 and or xor
+ * @param mixed $field 查询字段
+ * @param mixed $op 查询表达式
+ * @param mixed $condition 查询条件
+ * @param array $param 查询参数
+ * @param bool $strict 严格模式
+ * @return $this
+ */
+ protected function parseWhereExp(string $logic, $field, $op, $condition, array $param = [], bool $strict = false)
+ {
+ $logic = strtoupper($logic);
+
+ if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) {
+ $field = $this->options['via'] . '.' . $field;
+ }
+
+ if ($field instanceof Raw) {
+ return $this->whereRaw($field, is_array($op) ? $op : [], $logic);
+ } elseif ($strict) {
+ // 使用严格模式查询
+ if ('=' == $op) {
+ $where = $this->whereEq($field, $condition);
+ } else {
+ $where = [$field, $op, $condition, $logic];
+ }
+ } elseif (is_array($field)) {
+ // 解析数组批量查询
+ return $this->parseArrayWhereItems($field, $logic);
+ } elseif ($field instanceof Closure) {
+ $where = $field;
+ } elseif (is_string($field)) {
+ if (preg_match('/[,=\<\'\"\(\s]/', $field)) {
+ return $this->whereRaw($field, is_array($op) ? $op : [], $logic);
+ } elseif (is_string($op) && strtolower($op) == 'exp' && !is_null($condition)) {
+ $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : [];
+ return $this->whereExp($field, $condition, $bind, $logic);
+ }
+
+ $where = $this->parseWhereItem($logic, $field, $op, $condition, $param);
+ }
+
+ if (!empty($where)) {
+ $this->options['where'][$logic][] = $where;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 分析查询表达式
+ * @access protected
+ * @param string $logic 查询逻辑 and or xor
+ * @param mixed $field 查询字段
+ * @param mixed $op 查询表达式
+ * @param mixed $condition 查询条件
+ * @param array $param 查询参数
+ * @return array
+ */
+ protected function parseWhereItem(string $logic, $field, $op, $condition, array $param = []): array
+ {
+ if (is_array($op)) {
+ // 同一字段多条件查询
+ array_unshift($param, $field);
+ $where = $param;
+ } elseif ($field && is_null($condition)) {
+ if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
+ // null查询
+ $where = [$field, $op, ''];
+ } elseif ('=' === $op || is_null($op)) {
+ $where = [$field, 'NULL', ''];
+ } elseif ('<>' === $op) {
+ $where = [$field, 'NOTNULL', ''];
+ } else {
+ // 字段相等查询
+ $where = $this->whereEq($field, $op);
+ }
+ } elseif (is_string($op) && in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) {
+ $where = [$field, $op, is_string($condition) ? new Raw($condition) : $condition];
+ } else {
+ $where = $field ? [$field, $op, $condition, $param[2] ?? null] : [];
+ }
+
+ return $where;
+ }
+
+ /**
+ * 相等查询的主键处理
+ * @access protected
+ * @param string $field 字段名
+ * @param mixed $value 字段值
+ * @return array
+ */
+ protected function whereEq(string $field, $value): array
+ {
+ if ($this->getPk() == $field) {
+ $this->options['key'] = $value;
+ }
+
+ return [$field, '=', $value];
+ }
+
+ /**
+ * 数组批量查询
+ * @access protected
+ * @param array $field 批量查询
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ protected function parseArrayWhereItems(array $field, string $logic)
+ {
+ if (key($field) !== 0) {
+ $where = [];
+ foreach ($field as $key => $val) {
+ if ($val instanceof Raw) {
+ $where[] = [$key, 'exp', $val];
+ } else {
+ $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val];
+ }
+ }
+ } else {
+ // 数组批量查询
+ $where = $field;
+ }
+
+ if (!empty($where)) {
+ $this->options['where'][$logic] = isset($this->options['where'][$logic]) ?
+ array_merge($this->options['where'][$logic], $where) : $where;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 去除某个查询条件
+ * @access public
+ * @param string $field 查询字段
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function removeWhereField(string $field, string $logic = 'AND')
+ {
+ $logic = strtoupper($logic);
+
+ if (isset($this->options['where'][$logic])) {
+ foreach ($this->options['where'][$logic] as $key => $val) {
+ if (is_array($val) && $val[0] == $field) {
+ unset($this->options['where'][$logic][$key]);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 条件查询
+ * @access public
+ * @param mixed $condition 满足条件(支持闭包)
+ * @param Closure|array $query 满足条件后执行的查询表达式(闭包或数组)
+ * @param Closure|array $otherwise 不满足条件后执行
+ * @return $this
+ */
+ public function when($condition, $query, $otherwise = null)
+ {
+ if ($condition instanceof Closure) {
+ $condition = $condition($this);
+ }
+
+ if ($condition) {
+ if ($query instanceof Closure) {
+ $query($this, $condition);
+ } elseif (is_array($query)) {
+ $this->where($query);
+ }
+ } elseif ($otherwise) {
+ if ($otherwise instanceof Closure) {
+ $otherwise($this, $condition);
+ } elseif (is_array($otherwise)) {
+ $this->where($otherwise);
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/connector/Mongo.php b/vendor/topthink/think-orm/src/db/connector/Mongo.php
new file mode 100644
index 000000000..4b05b791d
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/connector/Mongo.php
@@ -0,0 +1,1176 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\connector;
+
+use Closure;
+use MongoDB\BSON\ObjectID;
+use MongoDB\Driver\BulkWrite;
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Exception\AuthenticationException;
+use MongoDB\Driver\Exception\BulkWriteException;
+use MongoDB\Driver\Exception\ConnectionException;
+use MongoDB\Driver\Exception\InvalidArgumentException;
+use MongoDB\Driver\Exception\RuntimeException;
+use MongoDB\Driver\Manager;
+use MongoDB\Driver\Query as MongoQuery;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\WriteConcern;
+use think\db\BaseQuery;
+use think\db\builder\Mongo as Builder;
+use think\db\Connection;
+use think\db\exception\DbException as Exception;
+use think\db\Mongo as Query;
+use function implode;
+use function is_array;
+
+/**
+ * Mongo数据库驱动
+ * @property Manager[] $links
+ * @property Manager $linkRead
+ * @property Manager $linkWrite
+ */
+class Mongo extends Connection
+{
+
+ // 查询数据类型
+ protected $dbName = '';
+ protected $typeMap = 'array';
+ protected $mongo; // MongoDb Object
+ protected $cursor; // MongoCursor Object
+ protected $session_uuid; // sessions会话列表当前会话数组key 随机生成
+ protected $sessions = []; // 会话列表
+
+ /** @var Builder */
+ protected $builder;
+
+ // 数据库连接参数配置
+ protected $config = [
+ // 数据库类型
+ 'type' => '',
+ // 服务器地址
+ 'hostname' => '',
+ // 数据库名
+ 'database' => '',
+ // 是否是复制集
+ 'is_replica_set' => false,
+ // 用户名
+ 'username' => '',
+ // 密码
+ 'password' => '',
+ // 端口
+ 'hostport' => '',
+ // 连接dsn
+ 'dsn' => '',
+ // 数据库连接参数
+ 'params' => [],
+ // 数据库编码默认采用utf8
+ 'charset' => 'utf8',
+ // 主键名
+ 'pk' => '_id',
+ // 主键类型
+ 'pk_type' => 'ObjectID',
+ // 数据库表前缀
+ 'prefix' => '',
+ // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
+ 'deploy' => 0,
+ // 数据库读写是否分离 主从式有效
+ 'rw_separate' => false,
+ // 读写分离后 主服务器数量
+ 'master_num' => 1,
+ // 指定从服务器序号
+ 'slave_no' => '',
+ // 是否严格检查字段是否存在
+ 'fields_strict' => true,
+ // 开启字段缓存
+ 'fields_cache' => false,
+ // 监听SQL
+ 'trigger_sql' => true,
+ // 自动写入时间戳字段
+ 'auto_timestamp' => false,
+ // 时间字段取出后的默认时间格式
+ 'datetime_format' => 'Y-m-d H:i:s',
+ // 是否_id转换为id
+ 'pk_convert_id' => false,
+ // typeMap
+ 'type_map' => ['root' => 'array', 'document' => 'array'],
+ ];
+
+ /**
+ * 获取当前连接器类对应的Query类
+ * @access public
+ * @return string
+ */
+ public function getQueryClass(): string
+ {
+ return Query::class;
+ }
+
+ /**
+ * 获取当前的builder实例对象
+ * @access public
+ * @return Builder
+ */
+ public function getBuilder()
+ {
+ return $this->builder;
+ }
+
+ /**
+ * 获取当前连接器类对应的Builder类
+ * @access public
+ * @return string
+ */
+ public function getBuilderClass(): string
+ {
+ return Builder::class;
+ }
+
+ /**
+ * 连接数据库方法
+ * @access public
+ * @param array $config 连接参数
+ * @param integer $linkNum 连接序号
+ * @return Manager
+ * @throws InvalidArgumentException
+ * @throws RuntimeException
+ */
+ public function connect(array $config = [], $linkNum = 0)
+ {
+ if (!isset($this->links[$linkNum])) {
+ if (empty($config)) {
+ $config = $this->config;
+ } else {
+ $config = array_merge($this->config, $config);
+ }
+
+ $this->dbName = $config['database'];
+ $this->typeMap = $config['type_map'];
+
+ if ($config['pk_convert_id'] && '_id' == $config['pk']) {
+ $this->config['pk'] = 'id';
+ }
+
+ if (empty($config['dsn'])) {
+ $config['dsn'] = 'mongodb://' . ($config['username'] ? "{$config['username']}" : '') . ($config['password'] ? ":{$config['password']}@" : '') . $config['hostname'] . ($config['hostport'] ? ":{$config['hostport']}" : '');
+ }
+
+ $startTime = microtime(true);
+
+ $this->links[$linkNum] = new Manager($config['dsn'], $config['params']);
+
+ if (!empty($config['trigger_sql'])) {
+ // 记录数据库连接信息
+ $this->trigger('CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']);
+ }
+
+ }
+
+ return $this->links[$linkNum];
+ }
+
+ /**
+ * 获取Mongo Manager对象
+ * @access public
+ * @return Manager|null
+ */
+ public function getMongo()
+ {
+ return $this->mongo ?: null;
+ }
+
+ /**
+ * 设置/获取当前操作的database
+ * @access public
+ * @param string $db db
+ * @return string
+ */
+ public function db(string $db = null)
+ {
+ if (is_null($db)) {
+ return $this->dbName;
+ } else {
+ $this->dbName = $db;
+ }
+ }
+
+ /**
+ * 执行查询但只返回Cursor对象
+ * @access public
+ * @param Query $query 查询对象
+ * @return Cursor
+ */
+ public function cursor($query)
+ {
+ // 分析查询表达式
+ $options = $query->parseOptions();
+
+ // 生成MongoQuery对象
+ $mongoQuery = $this->builder->select($query);
+
+ $master = $query->getOptions('master') ? true : false;
+
+ // 执行查询操作
+ return $this->getCursor($query, $mongoQuery, $master);
+ }
+
+ /**
+ * 执行查询并返回Cursor对象
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param MongoQuery|Closure $mongoQuery Mongo查询对象
+ * @param bool $master 是否主库操作
+ * @return Cursor
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ */
+ public function getCursor(BaseQuery $query, $mongoQuery, bool $master = false): Cursor
+ {
+ $this->initConnect($master);
+ $this->db->updateQueryTimes();
+
+ $options = $query->getOptions();
+ $namespace = $options['table'];
+
+ if (false === strpos($namespace, '.')) {
+ $namespace = $this->dbName . '.' . $namespace;
+ }
+
+ if (!empty($this->queryStr)) {
+ // 记录执行指令
+ $this->queryStr = 'db' . strstr($namespace, '.') . '.' . $this->queryStr;
+ }
+
+ if ($mongoQuery instanceof Closure) {
+ $mongoQuery = $mongoQuery($query);
+ }
+
+ $readPreference = $options['readPreference'] ?? null;
+ $this->queryStartTime = microtime(true);
+
+ if ($session = $this->getSession()) {
+ $this->cursor = $this->mongo->executeQuery($namespace, $query, [
+ 'readPreference' => is_null($readPreference) ? new ReadPreference(ReadPreference::RP_PRIMARY) : $readPreference,
+ 'session' => $session,
+ ]);
+ } else {
+ $this->cursor = $this->mongo->executeQuery($namespace, $mongoQuery, $readPreference);
+ }
+
+ // SQL监控
+ if (!empty($this->config['trigger_sql'])) {
+ $this->trigger('', $master);
+ }
+
+ return $this->cursor;
+ }
+
+ /**
+ * 执行查询 返回数据集
+ * @access public
+ * @param MongoQuery $query 查询对象
+ * @return mixed
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ */
+ public function query(MongoQuery $query)
+ {
+ return $this->mongoQuery($this->newQuery(), $query);
+ }
+
+ /**
+ * 执行语句
+ * @access public
+ * @param BulkWrite $bulk
+ * @return int
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ * @throws BulkWriteException
+ */
+ public function execute(BulkWrite $bulk)
+ {
+ return $this->mongoExecute($this->newQuery(), $bulk);
+ }
+
+ /**
+ * 执行查询
+ * @access protected
+ * @param BaseQuery $query 查询对象
+ * @param MongoQuery|Closure $mongoQuery Mongo查询对象
+ * @return array
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ */
+ protected function mongoQuery(BaseQuery $query, $mongoQuery): array
+ {
+ $options = $query->parseOptions();
+
+ if ($query->getOptions('cache')) {
+ // 检查查询缓存
+ $cacheItem = $this->parseCache($query, $query->getOptions('cache'));
+ $key = $cacheItem->getKey();
+
+ if ($this->cache->has($key)) {
+ return $this->cache->get($key);
+ }
+ }
+
+ if ($mongoQuery instanceof Closure) {
+ $mongoQuery = $mongoQuery($query);
+ }
+
+ $master = $query->getOptions('master') ? true : false;
+ $this->getCursor($query, $mongoQuery, $master);
+
+ $resultSet = $this->getResult($options['typeMap']);
+
+ if (isset($cacheItem) && $resultSet) {
+ // 缓存数据集
+ $cacheItem->set($resultSet);
+ $this->cacheData($cacheItem);
+ }
+
+ return $resultSet;
+ }
+
+ /**
+ * 执行写操作
+ * @access protected
+ * @param BaseQuery $query
+ * @param BulkWrite $bulk
+ *
+ * @return WriteResult
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ * @throws BulkWriteException
+ */
+ protected function mongoExecute(BaseQuery $query, BulkWrite $bulk)
+ {
+ $this->initConnect(true);
+ $this->db->updateQueryTimes();
+
+ $options = $query->getOptions();
+
+ $namespace = $options['table'];
+ if (false === strpos($namespace, '.')) {
+ $namespace = $this->dbName . '.' . $namespace;
+ }
+
+ if (!empty($this->queryStr)) {
+ // 记录执行指令
+ $this->queryStr = 'db' . strstr($namespace, '.') . '.' . $this->queryStr;
+ }
+
+ $writeConcern = $options['writeConcern'] ?? null;
+ $this->queryStartTime = microtime(true);
+
+ if ($session = $this->getSession()) {
+ $writeResult = $this->mongo->executeBulkWrite($namespace, $bulk, [
+ 'session' => $session,
+ 'writeConcern' => is_null($writeConcern) ? new WriteConcern(1) : $writeConcern,
+ ]);
+ } else {
+ $writeResult = $this->mongo->executeBulkWrite($namespace, $bulk, $writeConcern);
+ }
+
+ // SQL监控
+ if (!empty($this->config['trigger_sql'])) {
+ $this->trigger();
+ }
+
+ $this->numRows = $writeResult->getMatchedCount();
+
+ if ($query->getOptions('cache')) {
+ // 清理缓存数据
+ $cacheItem = $this->parseCache($query, $query->getOptions('cache'));
+ $key = $cacheItem->getKey();
+ $tag = $cacheItem->getTag();
+
+ if (isset($key) && $this->cache->has($key)) {
+ $this->cache->delete($key);
+ } elseif (!empty($tag) && method_exists($this->cache, 'tag')) {
+ $this->cache->tag($tag)->clear();
+ }
+ }
+
+ return $writeResult;
+ }
+
+ /**
+ * 执行指令
+ * @access public
+ * @param Command $command 指令
+ * @param string $dbName 当前数据库名
+ * @param ReadPreference $readPreference readPreference
+ * @param string|array $typeMap 指定返回的typeMap
+ * @param bool $master 是否主库操作
+ * @return array
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ */
+ public function command(Command $command, string $dbName = '', ReadPreference $readPreference = null, $typeMap = null, bool $master = false): array
+ {
+ $this->initConnect($master);
+ $this->db->updateQueryTimes();
+
+ $this->queryStartTime = microtime(true);
+
+ $dbName = $dbName ?: $this->dbName;
+
+ if (!empty($this->queryStr)) {
+ $this->queryStr = 'db.' . $this->queryStr;
+ }
+
+ if ($session = $this->getSession()) {
+ $this->cursor = $this->mongo->executeCommand($dbName, $command, [
+ 'readPreference' => is_null($readPreference) ? new ReadPreference(ReadPreference::RP_PRIMARY) : $readPreference,
+ 'session' => $session,
+ ]);
+ } else {
+ $this->cursor = $this->mongo->executeCommand($dbName, $command, $readPreference);
+ }
+
+ // SQL监控
+ if (!empty($this->config['trigger_sql'])) {
+ $this->trigger('', $master);
+ }
+
+ return $this->getResult($typeMap);
+ }
+
+ /**
+ * 获得数据集
+ * @access protected
+ * @param string|array $typeMap 指定返回的typeMap
+ * @return mixed
+ */
+ protected function getResult($typeMap = null): array
+ {
+ // 设置结果数据类型
+ if (is_null($typeMap)) {
+ $typeMap = $this->typeMap;
+ }
+
+ $typeMap = is_string($typeMap) ? ['root' => $typeMap] : $typeMap;
+
+ $this->cursor->setTypeMap($typeMap);
+
+ // 获取数据集
+ $result = $this->cursor->toArray();
+
+ if ($this->getConfig('pk_convert_id')) {
+ // 转换ObjectID 字段
+ foreach ($result as &$data) {
+ $this->convertObjectID($data);
+ }
+ }
+
+ $this->numRows = count($result);
+
+ return $result;
+ }
+
+ /**
+ * ObjectID处理
+ * @access protected
+ * @param array $data 数据
+ * @return void
+ */
+ protected function convertObjectID(array &$data): void
+ {
+ if (isset($data['_id']) && is_object($data['_id'])) {
+ $data['id'] = $data['_id']->__toString();
+ unset($data['_id']);
+ }
+ }
+
+ /**
+ * 数据库日志记录(仅供参考)
+ * @access public
+ * @param string $type 类型
+ * @param mixed $data 数据
+ * @param array $options 参数
+ * @return void
+ */
+ public function mongoLog(string $type, $data, array $options = [])
+ {
+ if (!$this->config['trigger_sql']) {
+ return;
+ }
+
+ if (is_array($data)) {
+ array_walk_recursive($data, function (&$value) {
+ if ($value instanceof ObjectID) {
+ $value = $value->__toString();
+ }
+ });
+ }
+
+ switch (strtolower($type)) {
+ case 'aggregate':
+ $this->queryStr = 'runCommand(' . ($data ? json_encode($data) : '') . ');';
+ break;
+ case 'find':
+ $this->queryStr = $type . '(' . ($data ? json_encode($data) : '') . ')';
+
+ if (isset($options['sort'])) {
+ $this->queryStr .= '.sort(' . json_encode($options['sort']) . ')';
+ }
+
+ if (isset($options['skip'])) {
+ $this->queryStr .= '.skip(' . $options['skip'] . ')';
+ }
+
+ if (isset($options['limit'])) {
+ $this->queryStr .= '.limit(' . $options['limit'] . ')';
+ }
+
+ $this->queryStr .= ';';
+ break;
+ case 'insert':
+ case 'remove':
+ $this->queryStr = $type . '(' . ($data ? json_encode($data) : '') . ');';
+ break;
+ case 'update':
+ $this->queryStr = $type . '(' . json_encode($options) . ',' . json_encode($data) . ');';
+ break;
+ case 'cmd':
+ $this->queryStr = $data . '(' . json_encode($options) . ');';
+ break;
+ }
+
+ $this->options = $options;
+ }
+
+ /**
+ * 获取最近执行的指令
+ * @access public
+ * @return string
+ */
+ public function getLastSql(): string
+ {
+ return $this->queryStr;
+ }
+
+ /**
+ * 关闭数据库
+ * @access public
+ */
+ public function close()
+ {
+ $this->mongo = null;
+ $this->cursor = null;
+ $this->linkRead = null;
+ $this->linkWrite = null;
+ $this->links = [];
+ }
+
+ /**
+ * 初始化数据库连接
+ * @access protected
+ * @param boolean $master 是否主服务器
+ * @return void
+ */
+ protected function initConnect(bool $master = true): void
+ {
+ if (!empty($this->config['deploy'])) {
+ // 采用分布式数据库
+ if ($master) {
+ if (!$this->linkWrite) {
+ $this->linkWrite = $this->multiConnect(true);
+ }
+
+ $this->mongo = $this->linkWrite;
+ } else {
+ if (!$this->linkRead) {
+ $this->linkRead = $this->multiConnect(false);
+ }
+
+ $this->mongo = $this->linkRead;
+ }
+ } elseif (!$this->mongo) {
+ // 默认单数据库
+ $this->mongo = $this->connect();
+ }
+ }
+
+ /**
+ * 连接分布式服务器
+ * @access protected
+ * @param boolean $master 主服务器
+ * @return Manager
+ */
+ protected function multiConnect(bool $master = false): Manager
+ {
+ $config = [];
+ // 分布式数据库配置解析
+ foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn'] as $name) {
+ $config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $this->config[$name];
+ }
+
+ // 主服务器序号
+ $m = floor(mt_rand(0, $this->config['master_num'] - 1));
+
+ if ($this->config['rw_separate']) {
+ // 主从式采用读写分离
+ if ($master) // 主服务器写入
+ {
+ if ($this->config['is_replica_set']) {
+ return $this->replicaSetConnect();
+ } else {
+ $r = $m;
+ }
+ } elseif (is_numeric($this->config['slave_no'])) {
+ // 指定服务器读
+ $r = $this->config['slave_no'];
+ } else {
+ // 读操作连接从服务器 每次随机连接的数据库
+ $r = floor(mt_rand($this->config['master_num'], count($config['hostname']) - 1));
+ }
+ } else {
+ // 读写操作不区分服务器 每次随机连接的数据库
+ $r = floor(mt_rand(0, count($config['hostname']) - 1));
+ }
+
+ $dbConfig = [];
+
+ foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn'] as $name) {
+ $dbConfig[$name] = $config[$name][$r] ?? $config[$name][0];
+ }
+
+ return $this->connect($dbConfig, $r);
+ }
+
+ /**
+ * 创建基于复制集的连接
+ * @return Manager
+ */
+ public function replicaSetConnect(): Manager
+ {
+ $this->dbName = $this->config['database'];
+ $this->typeMap = $this->config['type_map'];
+
+ $startTime = microtime(true);
+
+ $this->config['params']['replicaSet'] = $this->config['database'];
+
+ $manager = new Manager($this->buildUrl(), $this->config['params']);
+
+ // 记录数据库连接信息
+ if (!empty($config['trigger_sql'])) {
+ $this->trigger('CONNECT:ReplicaSet[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $this->config['dsn']);
+ }
+
+ return $manager;
+ }
+
+ /**
+ * 根据配置信息 生成适用于连接复制集的 URL
+ * @return string
+ */
+ private function buildUrl(): string
+ {
+ $url = 'mongodb://' . ($this->config['username'] ? "{$this->config['username']}" : '') . ($this->config['password'] ? ":{$this->config['password']}@" : '');
+
+ $hostList = is_string($this->config['hostname']) ? explode(',', $this->config['hostname']) : $this->config['hostname'];
+ $portList = is_string($this->config['hostport']) ? explode(',', $this->config['hostport']) : $this->config['hostport'];
+
+ for ($i = 0; $i < count($hostList); $i++) {
+ $url = $url . $hostList[$i] . ':' . $portList[0] . ',';
+ }
+
+ return rtrim($url, ",") . '/';
+ }
+
+ /**
+ * 插入记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param boolean $getLastInsID 返回自增主键
+ * @return mixed
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ * @throws BulkWriteException
+ */
+ public function insert(BaseQuery $query, bool $getLastInsID = false)
+ {
+ // 分析查询表达式
+ $options = $query->parseOptions();
+
+ if (empty($options['data'])) {
+ throw new Exception('miss data to insert');
+ }
+
+ // 生成bulk对象
+ $bulk = $this->builder->insert($query);
+
+ $writeResult = $this->mongoExecute($query, $bulk);
+ $result = $writeResult->getInsertedCount();
+
+ if ($result) {
+ $data = $options['data'];
+ $lastInsId = $this->getLastInsID($query);
+
+ if ($lastInsId) {
+ $pk = $query->getPk();
+ $data[$pk] = $lastInsId;
+ }
+
+ $query->setOption('data', $data);
+
+ $this->db->trigger('after_insert', $query);
+
+ if ($getLastInsID) {
+ return $lastInsId;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 获取最近插入的ID
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return mixed
+ */
+ public function getLastInsID(BaseQuery $query)
+ {
+ $id = $this->builder->getLastInsID();
+
+ if (is_array($id)) {
+ array_walk($id, function (&$item, $key) {
+ if ($item instanceof ObjectID) {
+ $item = $item->__toString();
+ }
+ });
+ } elseif ($id instanceof ObjectID) {
+ $id = $id->__toString();
+ }
+
+ return $id;
+ }
+
+ /**
+ * 批量插入记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param array $dataSet 数据集
+ * @return integer
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ * @throws BulkWriteException
+ */
+ public function insertAll(BaseQuery $query, array $dataSet = []): int
+ {
+ // 分析查询表达式
+ $query->parseOptions();
+
+ if (!is_array(reset($dataSet))) {
+ return 0;
+ }
+
+ // 生成bulkWrite对象
+ $bulk = $this->builder->insertAll($query, $dataSet);
+
+ $writeResult = $this->mongoExecute($query, $bulk);
+
+ return $writeResult->getInsertedCount();
+ }
+
+ /**
+ * 更新记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return int
+ * @throws Exception
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ * @throws BulkWriteException
+ */
+ public function update(BaseQuery $query): int
+ {
+ $query->parseOptions();
+
+ // 生成bulkWrite对象
+ $bulk = $this->builder->update($query);
+
+ $writeResult = $this->mongoExecute($query, $bulk);
+
+ $result = $writeResult->getModifiedCount();
+
+ if ($result) {
+ $this->db->trigger('after_update', $query);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return int
+ * @throws Exception
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ * @throws BulkWriteException
+ */
+ public function delete(BaseQuery $query): int
+ {
+ // 分析查询表达式
+ $query->parseOptions();
+
+ // 生成bulkWrite对象
+ $bulk = $this->builder->delete($query);
+
+ // 执行操作
+ $writeResult = $this->mongoExecute($query, $bulk);
+
+ $result = $writeResult->getDeletedCount();
+
+ if ($result) {
+ $this->db->trigger('after_delete', $query);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 查找记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return array
+ * @throws ModelNotFoundException
+ * @throws DataNotFoundException
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ */
+ public function select(BaseQuery $query): array
+ {
+ $resultSet = $this->db->trigger('before_select', $query);
+
+ if (!$resultSet) {
+ $resultSet = $this->mongoQuery($query, function ($query) {
+ return $this->builder->select($query);
+ });
+ }
+
+ return $resultSet;
+ }
+
+ /**
+ * 查找单条记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return array
+ * @throws ModelNotFoundException
+ * @throws DataNotFoundException
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ */
+ public function find(BaseQuery $query): array
+ {
+ // 事件回调
+ $result = $this->db->trigger('before_find', $query);
+
+ if (!$result) {
+ // 执行查询
+ $resultSet = $this->mongoQuery($query, function ($query) {
+ return $this->builder->select($query, true);
+ });
+
+ $result = $resultSet[0] ?? [];
+ }
+
+ return $result;
+ }
+
+ /**
+ * 得到某个字段的值
+ * @access public
+ * @param string $field 字段名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function value(BaseQuery $query, string $field, $default = null)
+ {
+ $options = $query->parseOptions();
+
+ if (isset($options['projection'])) {
+ $query->removeOption('projection');
+ }
+
+ $query->setOption('projection', (array) $field);
+
+ if (!empty($options['cache'])) {
+ $cacheItem = $this->parseCache($query, $options['cache']);
+ $key = $cacheItem->getKey();
+
+ if ($this->cache->has($key)) {
+ return $this->cache->get($key);
+ }
+ }
+
+ $mongoQuery = $this->builder->select($query, true);
+
+ if (isset($options['projection'])) {
+ $query->setOption('projection', $options['projection']);
+ } else {
+ $query->removeOption('projection');
+ }
+
+ // 执行查询操作
+ $resultSet = $this->mongoQuery($query, $mongoQuery);
+
+ if (!empty($resultSet)) {
+ $data = array_shift($resultSet);
+ $result = $data[$field];
+ } else {
+ $result = false;
+ }
+
+ if (isset($cacheItem) && false !== $result) {
+ // 缓存数据
+ $cacheItem->set($result);
+ $this->cacheData($cacheItem);
+ }
+
+ return false !== $result ? $result : $default;
+ }
+
+ /**
+ * 得到某个列的数组
+ * @access public
+ * @param BaseQuery $query
+ * @param string|array $field 字段名 多个字段用逗号分隔
+ * @param string $key 索引
+ * @return array
+ */
+ public function column(BaseQuery $query, $field, string $key = ''): array
+ {
+ $options = $query->parseOptions();
+
+ if (isset($options['projection'])) {
+ $query->removeOption('projection');
+ }
+
+ if (is_array($field)) {
+ $field = implode(',', $field);
+ }
+ if ($key && '*' != $field) {
+ $projection = $key . ',' . $field;
+ } else {
+ $projection = $field;
+ }
+
+ $query->field($projection);
+
+ if (!empty($options['cache'])) {
+ // 判断查询缓存
+ $cacheItem = $this->parseCache($query, $options['cache']);
+ $key = $cacheItem->getKey();
+
+ if ($this->cache->has($key)) {
+ return $this->cache->get($key);
+ }
+ }
+
+ $mongoQuery = $this->builder->select($query);
+
+ if (isset($options['projection'])) {
+ $query->setOption('projection', $options['projection']);
+ } else {
+ $query->removeOption('projection');
+ }
+
+ // 执行查询操作
+ $resultSet = $this->mongoQuery($query, $mongoQuery);
+
+ if (('*' == $field || strpos($field, ',')) && $key) {
+ $result = array_column($resultSet, null, $key);
+ } elseif (!empty($resultSet)) {
+ $result = array_column($resultSet, $field, $key);
+ } else {
+ $result = [];
+ }
+
+ if (isset($cacheItem)) {
+ // 缓存数据
+ $cacheItem->set($result);
+ $this->cacheData($cacheItem);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 执行command
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string|array|object $command 指令
+ * @param mixed $extra 额外参数
+ * @param string $db 数据库名
+ * @return array
+ */
+ public function cmd(BaseQuery $query, $command, $extra = null, string $db = ''): array
+ {
+ if (is_array($command) || is_object($command)) {
+
+ $this->mongoLog('cmd', 'cmd', $command);
+
+ // 直接创建Command对象
+ $command = new Command($command);
+ } else {
+ // 调用Builder封装的Command对象
+ $command = $this->builder->$command($query, $extra);
+ }
+
+ return $this->command($command, $db);
+ }
+
+ /**
+ * 获取数据库字段
+ * @access public
+ * @param mixed $tableName 数据表名
+ * @return array
+ */
+ public function getTableFields($tableName): array
+ {
+ return [];
+ }
+
+ /**
+ * 执行数据库事务
+ * @access public
+ * @param callable $callback 数据操作方法回调
+ * @return mixed
+ * @throws PDOException
+ * @throws \Exception
+ * @throws \Throwable
+ */
+ public function transaction(callable $callback)
+ {
+ $this->startTrans();
+ try {
+ $result = null;
+ if (is_callable($callback)) {
+ $result = call_user_func_array($callback, [$this]);
+ }
+ $this->commit();
+ return $result;
+ } catch (\Exception $e) {
+ $this->rollback();
+ throw $e;
+ } catch (\Throwable $e) {
+ $this->rollback();
+ throw $e;
+ }
+ }
+
+ /**
+ * 启动事务
+ * @access public
+ * @return void
+ * @throws \PDOException
+ * @throws \Exception
+ */
+ public function startTrans()
+ {
+ $this->initConnect(true);
+ $this->session_uuid = uniqid();
+ $this->sessions[$this->session_uuid] = $this->getMongo()->startSession();
+
+ $this->sessions[$this->session_uuid]->startTransaction([]);
+ }
+
+ /**
+ * 用于非自动提交状态下面的查询提交
+ * @access public
+ * @return void
+ * @throws PDOException
+ */
+ public function commit()
+ {
+ if ($session = $this->getSession()) {
+ $session->commitTransaction();
+ $this->setLastSession();
+ }
+ }
+
+ /**
+ * 事务回滚
+ * @access public
+ * @return void
+ * @throws PDOException
+ */
+ public function rollback()
+ {
+ if ($session = $this->getSession()) {
+ $session->abortTransaction();
+ $this->setLastSession();
+ }
+ }
+
+ /**
+ * 结束当前会话,设置上一个会话为当前会话
+ * @author klinson
+ */
+ protected function setLastSession()
+ {
+ if ($session = $this->getSession()) {
+ $session->endSession();
+ unset($this->sessions[$this->session_uuid]);
+ if (empty($this->sessions)) {
+ $this->session_uuid = null;
+ } else {
+ end($this->sessions);
+ $this->session_uuid = key($this->sessions);
+ }
+ }
+ }
+
+ /**
+ * 获取当前会话
+ * @return \MongoDB\Driver\Session|null
+ * @author klinson
+ */
+ public function getSession()
+ {
+ return ($this->session_uuid && isset($this->sessions[$this->session_uuid]))
+ ? $this->sessions[$this->session_uuid]
+ : null;
+ }
+}
diff --git a/thinkphp/library/think/db/connector/Mysql.php b/vendor/topthink/think-orm/src/db/connector/Mysql.php
old mode 100755
new mode 100644
similarity index 55%
rename from thinkphp/library/think/db/connector/Mysql.php
rename to vendor/topthink/think-orm/src/db/connector/Mysql.php
index 93b8a1825..483b447a8
--- a/thinkphp/library/think/db/connector/Mysql.php
+++ b/vendor/topthink/think-orm/src/db/connector/Mysql.php
@@ -2,56 +2,32 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\db\connector;
use PDO;
-use think\db\Connection;
-use think\db\Query;
+use think\db\PDOConnection;
/**
* mysql数据库驱动
*/
-class Mysql extends Connection
+class Mysql extends PDOConnection
{
- protected $builder = '\\think\\db\\builder\\Mysql';
-
- /**
- * 初始化
- * @access protected
- * @return void
- */
- protected function initialize()
- {
- // Point类型支持
- Query::extend('point', function ($query, $field, $value = null, $fun = 'GeomFromText', $type = 'POINT') {
- if (!is_null($value)) {
- $query->data($field, ['point', $value, $fun, $type]);
- } else {
- if (is_string($field)) {
- $field = explode(',', $field);
- }
- $query->setOption('point', $field);
- }
-
- return $query;
- });
- }
-
/**
* 解析pdo连接的dsn信息
* @access protected
* @param array $config 连接信息
* @return string
*/
- protected function parseDsn($config)
+ protected function parseDsn(array $config): string
{
if (!empty($config['socket'])) {
$dsn = 'mysql:unix_socket=' . $config['socket'];
@@ -75,9 +51,9 @@ class Mysql extends Connection
* @param string $tableName
* @return array
*/
- public function getFields($tableName)
+ public function getFields(string $tableName): array
{
- list($tableName) = explode(' ', $tableName);
+ [$tableName] = explode(' ', $tableName);
if (false === strpos($tableName, '`')) {
if (strpos($tableName, '.')) {
@@ -86,21 +62,23 @@ class Mysql extends Connection
$tableName = '`' . $tableName . '`';
}
- $sql = 'SHOW COLUMNS FROM ' . $tableName;
- $pdo = $this->query($sql, [], false, true);
+ $sql = 'SHOW FULL COLUMNS FROM ' . $tableName;
+ $pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
- if ($result) {
+ if (!empty($result)) {
foreach ($result as $key => $val) {
- $val = array_change_key_case($val);
+ $val = array_change_key_case($val);
+
$info[$val['field']] = [
'name' => $val['field'],
'type' => $val['type'],
- 'notnull' => (bool) ('' === $val['null']), // not null is empty, null is yes
+ 'notnull' => 'NO' == $val['null'],
'default' => $val['default'],
- 'primary' => (strtolower($val['key']) == 'pri'),
- 'autoinc' => (strtolower($val['extra']) == 'auto_increment'),
+ 'primary' => strtolower($val['key']) == 'pri',
+ 'autoinc' => strtolower($val['extra']) == 'auto_increment',
+ 'comment' => $val['comment'],
];
}
}
@@ -114,10 +92,10 @@ class Mysql extends Connection
* @param string $dbName
* @return array
*/
- public function getTables($dbName = '')
+ public function getTables(string $dbName = ''): array
{
$sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES ';
- $pdo = $this->query($sql, [], false, true);
+ $pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
@@ -128,28 +106,7 @@ class Mysql extends Connection
return $info;
}
- /**
- * SQL性能分析
- * @access protected
- * @param string $sql
- * @return array
- */
- protected function getExplain($sql)
- {
- $pdo = $this->linkID->query("EXPLAIN " . $sql);
- $result = $pdo->fetch(PDO::FETCH_ASSOC);
- $result = array_change_key_case($result);
-
- if (isset($result['extra'])) {
- if (strpos($result['extra'], 'filesort') || strpos($result['extra'], 'temporary')) {
- $this->log('SQL:' . $this->queryStr . '[' . $result['extra'] . ']', 'warn');
- }
- }
-
- return $result;
- }
-
- protected function supportSavepoint()
+ protected function supportSavepoint(): bool
{
return true;
}
@@ -160,14 +117,10 @@ class Mysql extends Connection
* @param string $xid XA事务id
* @return void
*/
- public function startTransXa($xid)
+ public function startTransXa(string $xid)
{
$this->initConnect(true);
- if (!$this->linkID) {
- return false;
- }
-
- $this->execute("XA START '$xid'");
+ $this->linkID->exec("XA START '$xid'");
}
/**
@@ -176,11 +129,11 @@ class Mysql extends Connection
* @param string $xid XA事务id
* @return void
*/
- public function prepareXa($xid)
+ public function prepareXa(string $xid)
{
$this->initConnect(true);
- $this->execute("XA END '$xid'");
- $this->execute("XA PREPARE '$xid'");
+ $this->linkID->exec("XA END '$xid'");
+ $this->linkID->exec("XA PREPARE '$xid'");
}
/**
@@ -189,10 +142,10 @@ class Mysql extends Connection
* @param string $xid XA事务id
* @return void
*/
- public function commitXa($xid)
+ public function commitXa(string $xid)
{
$this->initConnect(true);
- $this->execute("XA COMMIT '$xid'");
+ $this->linkID->exec("XA COMMIT '$xid'");
}
/**
@@ -201,9 +154,9 @@ class Mysql extends Connection
* @param string $xid XA事务id
* @return void
*/
- public function rollbackXa($xid)
+ public function rollbackXa(string $xid)
{
$this->initConnect(true);
- $this->execute("XA ROLLBACK '$xid'");
+ $this->linkID->exec("XA ROLLBACK '$xid'");
}
}
diff --git a/vendor/topthink/think-orm/src/db/connector/Oracle.php b/vendor/topthink/think-orm/src/db/connector/Oracle.php
new file mode 100644
index 000000000..236d8bfa1
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/connector/Oracle.php
@@ -0,0 +1,117 @@
+
+// +----------------------------------------------------------------------
+
+namespace think\db\connector;
+
+use PDO;
+use think\db\BaseQuery;
+use think\db\PDOConnection;
+
+/**
+ * Oracle数据库驱动
+ */
+class Oracle extends PDOConnection
+{
+ /**
+ * 解析pdo连接的dsn信息
+ * @access protected
+ * @param array $config 连接信息
+ * @return string
+ */
+ protected function parseDsn(array $config): string
+ {
+ $dsn = 'oci:dbname=';
+
+ if (!empty($config['hostname'])) {
+ // Oracle Instant Client
+ $dsn .= '//' . $config['hostname'] . ($config['hostport'] ? ':' . $config['hostport'] : '') . '/';
+ }
+
+ $dsn .= $config['database'];
+
+ if (!empty($config['charset'])) {
+ $dsn .= ';charset=' . $config['charset'];
+ }
+
+ return $dsn;
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @param string $tableName
+ * @return array
+ */
+ public function getFields(string $tableName): array
+ {
+ [$tableName] = explode(' ', $tableName);
+ $sql = "select a.column_name,data_type,DECODE (nullable, 'Y', 0, 1) notnull,data_default, DECODE (A .column_name,b.column_name,1,0) pk from all_tab_columns a,(select column_name from all_constraints c, all_cons_columns col where c.constraint_name = col.constraint_name and c.constraint_type = 'P' and c.table_name = '" . strtoupper($tableName) . "' ) b where table_name = '" . strtoupper($tableName) . "' and a.column_name = b.column_name (+)";
+
+ $pdo = $this->getPDOStatement($sql);
+ $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
+ $info = [];
+
+ if ($result) {
+ foreach ($result as $key => $val) {
+ $val = array_change_key_case($val);
+
+ $info[$val['column_name']] = [
+ 'name' => $val['column_name'],
+ 'type' => $val['data_type'],
+ 'notnull' => $val['notnull'],
+ 'default' => $val['data_default'],
+ 'primary' => $val['pk'],
+ 'autoinc' => $val['pk'],
+ ];
+ }
+ }
+
+ return $this->fieldCase($info);
+ }
+
+ /**
+ * 取得数据库的表信息(暂时实现取得用户表信息)
+ * @access public
+ * @param string $dbName
+ * @return array
+ */
+ public function getTables(string $dbName = ''): array
+ {
+ $sql = 'select table_name from all_tables';
+ $pdo = $this->getPDOStatement($sql);
+ $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
+ $info = [];
+
+ foreach ($result as $key => $val) {
+ $info[$key] = current($val);
+ }
+
+ return $info;
+ }
+
+ /**
+ * 获取最近插入的ID
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $sequence 自增序列名
+ * @return mixed
+ */
+ public function getLastInsID(BaseQuery $query, string $sequence = null)
+ {
+ $pdo = $this->linkID->query("select {$sequence}.currval as id from dual");
+ $result = $pdo->fetchColumn();
+
+ return $result;
+ }
+
+ protected function supportSavepoint(): bool
+ {
+ return true;
+ }
+}
diff --git a/thinkphp/library/think/db/connector/Pgsql.php b/vendor/topthink/think-orm/src/db/connector/Pgsql.php
old mode 100755
new mode 100644
similarity index 70%
rename from thinkphp/library/think/db/connector/Pgsql.php
rename to vendor/topthink/think-orm/src/db/connector/Pgsql.php
index ee9fca017..fec8f8401
--- a/thinkphp/library/think/db/connector/Pgsql.php
+++ b/vendor/topthink/think-orm/src/db/connector/Pgsql.php
@@ -2,7 +2,7 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
@@ -12,16 +12,18 @@
namespace think\db\connector;
use PDO;
-use think\db\Connection;
+use think\db\PDOConnection;
/**
* Pgsql数据库驱动
*/
-class Pgsql extends Connection
+class Pgsql extends PDOConnection
{
- protected $builder = '\\think\\db\\builder\\Pgsql';
- // PDO连接参数
+ /**
+ * 默认PDO连接参数
+ * @var array
+ */
protected $params = [
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
@@ -35,7 +37,7 @@ class Pgsql extends Connection
* @param array $config 连接信息
* @return string
*/
- protected function parseDsn($config)
+ protected function parseDsn(array $config): string
{
$dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname'];
@@ -52,18 +54,19 @@ class Pgsql extends Connection
* @param string $tableName
* @return array
*/
- public function getFields($tableName)
+ public function getFields(string $tableName): array
{
- list($tableName) = explode(' ', $tableName);
- $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');';
+ [$tableName] = explode(' ', $tableName);
+ $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');';
- $pdo = $this->query($sql, [], false, true);
+ $pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
- if ($result) {
+ if (!empty($result)) {
foreach ($result as $key => $val) {
- $val = array_change_key_case($val);
+ $val = array_change_key_case($val);
+
$info[$val['field']] = [
'name' => $val['field'],
'type' => $val['type'],
@@ -84,10 +87,10 @@ class Pgsql extends Connection
* @param string $dbName
* @return array
*/
- public function getTables($dbName = '')
+ public function getTables(string $dbName = ''): array
{
$sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'";
- $pdo = $this->query($sql, [], false, true);
+ $pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
@@ -98,18 +101,7 @@ class Pgsql extends Connection
return $info;
}
- /**
- * SQL性能分析
- * @access protected
- * @param string $sql
- * @return array
- */
- protected function getExplain($sql)
- {
- return [];
- }
-
- protected function supportSavepoint()
+ protected function supportSavepoint(): bool
{
return true;
}
diff --git a/thinkphp/library/think/db/connector/Sqlite.php b/vendor/topthink/think-orm/src/db/connector/Sqlite.php
old mode 100755
new mode 100644
similarity index 70%
rename from thinkphp/library/think/db/connector/Sqlite.php
rename to vendor/topthink/think-orm/src/db/connector/Sqlite.php
index 5b9b3fa64..c664f202d
--- a/thinkphp/library/think/db/connector/Sqlite.php
+++ b/vendor/topthink/think-orm/src/db/connector/Sqlite.php
@@ -2,7 +2,7 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
@@ -12,23 +12,21 @@
namespace think\db\connector;
use PDO;
-use think\db\Connection;
+use think\db\PDOConnection;
/**
* Sqlite数据库驱动
*/
-class Sqlite extends Connection
+class Sqlite extends PDOConnection
{
- protected $builder = '\\think\\db\\builder\\Sqlite';
-
/**
* 解析pdo连接的dsn信息
* @access protected
* @param array $config 连接信息
* @return string
*/
- protected function parseDsn($config)
+ protected function parseDsn(array $config): string
{
$dsn = 'sqlite:' . $config['database'];
@@ -41,18 +39,19 @@ class Sqlite extends Connection
* @param string $tableName
* @return array
*/
- public function getFields($tableName)
+ public function getFields(string $tableName): array
{
- list($tableName) = explode(' ', $tableName);
- $sql = 'PRAGMA table_info( ' . $tableName . ' )';
+ [$tableName] = explode(' ', $tableName);
+ $sql = 'PRAGMA table_info( ' . $tableName . ' )';
- $pdo = $this->query($sql, [], false, true);
+ $pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
- if ($result) {
+ if (!empty($result)) {
foreach ($result as $key => $val) {
- $val = array_change_key_case($val);
+ $val = array_change_key_case($val);
+
$info[$val['name']] = [
'name' => $val['name'],
'type' => $val['type'],
@@ -73,13 +72,13 @@ class Sqlite extends Connection
* @param string $dbName
* @return array
*/
- public function getTables($dbName = '')
+ public function getTables(string $dbName = ''): array
{
$sql = "SELECT name FROM sqlite_master WHERE type='table' "
. "UNION ALL SELECT name FROM sqlite_temp_master "
. "WHERE type='table' ORDER BY name";
- $pdo = $this->query($sql, [], false, true);
+ $pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
$info = [];
@@ -90,18 +89,7 @@ class Sqlite extends Connection
return $info;
}
- /**
- * SQL性能分析
- * @access protected
- * @param string $sql
- * @return array
- */
- protected function getExplain($sql)
- {
- return [];
- }
-
- protected function supportSavepoint()
+ protected function supportSavepoint(): bool
{
return true;
}
diff --git a/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php b/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php
new file mode 100644
index 000000000..10d944f37
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php
@@ -0,0 +1,122 @@
+
+// +----------------------------------------------------------------------
+
+namespace think\db\connector;
+
+use PDO;
+use think\db\PDOConnection;
+
+/**
+ * Sqlsrv数据库驱动
+ */
+class Sqlsrv extends PDOConnection
+{
+ /**
+ * 默认PDO连接参数
+ * @var array
+ */
+ protected $params = [
+ PDO::ATTR_CASE => PDO::CASE_NATURAL,
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
+ PDO::ATTR_STRINGIFY_FETCHES => false,
+ ];
+
+ /**
+ * 解析pdo连接的dsn信息
+ * @access protected
+ * @param array $config 连接信息
+ * @return string
+ */
+ protected function parseDsn(array $config): string
+ {
+ $dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname'];
+
+ if (!empty($config['hostport'])) {
+ $dsn .= ',' . $config['hostport'];
+ }
+
+ return $dsn;
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @param string $tableName
+ * @return array
+ */
+ public function getFields(string $tableName): array
+ {
+ [$tableName] = explode(' ', $tableName);
+ strpos($tableName,'.') && $tableName = substr($tableName,strpos($tableName,'.') + 1);
+ $sql = "SELECT column_name, data_type, column_default, is_nullable
+ FROM information_schema.tables AS t
+ JOIN information_schema.columns AS c
+ ON t.table_catalog = c.table_catalog
+ AND t.table_schema = c.table_schema
+ AND t.table_name = c.table_name
+ WHERE t.table_name = '$tableName'";
+
+ $pdo = $this->getPDOStatement($sql);
+ $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
+ $info = [];
+
+ if (!empty($result)) {
+ foreach ($result as $key => $val) {
+ $val = array_change_key_case($val);
+
+ $info[$val['column_name']] = [
+ 'name' => $val['column_name'],
+ 'type' => $val['data_type'],
+ 'notnull' => (bool) ('' === $val['is_nullable']), // not null is empty, null is yes
+ 'default' => $val['column_default'],
+ 'primary' => false,
+ 'autoinc' => false,
+ ];
+ }
+ }
+
+ $sql = "SELECT column_name FROM information_schema.key_column_usage WHERE table_name='$tableName'";
+ $pdo = $this->linkID->query($sql);
+ $result = $pdo->fetch(PDO::FETCH_ASSOC);
+
+ if ($result) {
+ $info[$result['column_name']]['primary'] = true;
+ }
+
+ return $this->fieldCase($info);
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @param string $dbName
+ * @return array
+ */
+ public function getTables(string $dbName = ''): array
+ {
+ $sql = "SELECT TABLE_NAME
+ FROM INFORMATION_SCHEMA.TABLES
+ WHERE TABLE_TYPE = 'BASE TABLE'
+ ";
+
+ $pdo = $this->getPDOStatement($sql);
+ $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
+ $info = [];
+
+ foreach ($result as $key => $val) {
+ $info[$key] = current($val);
+ }
+
+ return $info;
+ }
+
+}
diff --git a/thinkphp/library/think/db/connector/pgsql.sql b/vendor/topthink/think-orm/src/db/connector/pgsql.sql
old mode 100755
new mode 100644
similarity index 100%
rename from thinkphp/library/think/db/connector/pgsql.sql
rename to vendor/topthink/think-orm/src/db/connector/pgsql.sql
diff --git a/thinkphp/library/think/db/exception/BindParamException.php b/vendor/topthink/think-orm/src/db/exception/BindParamException.php
old mode 100755
new mode 100644
similarity index 83%
rename from thinkphp/library/think/db/exception/BindParamException.php
rename to vendor/topthink/think-orm/src/db/exception/BindParamException.php
index dce0c7bfc..08bb38804
--- a/thinkphp/library/think/db/exception/BindParamException.php
+++ b/vendor/topthink/think-orm/src/db/exception/BindParamException.php
@@ -2,17 +2,16 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\db\exception;
-use think\exception\DbException;
-
/**
* PDO参数绑定异常
*/
@@ -28,7 +27,7 @@ class BindParamException extends DbException
* @param array $bind
* @param int $code
*/
- public function __construct($message, $config, $sql, $bind, $code = 10502)
+ public function __construct(string $message, array $config, string $sql, array $bind, int $code = 10502)
{
$this->setData('Bind Param', $bind);
parent::__construct($message, $config, $sql, $code);
diff --git a/thinkphp/library/think/db/exception/DataNotFoundException.php b/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php
old mode 100755
new mode 100644
similarity index 86%
rename from thinkphp/library/think/db/exception/DataNotFoundException.php
rename to vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php
index 883e333e3..d10dd4330
--- a/thinkphp/library/think/db/exception/DataNotFoundException.php
+++ b/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php
@@ -2,17 +2,16 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\db\exception;
-use think\exception\DbException;
-
class DataNotFoundException extends DbException
{
protected $table;
@@ -24,7 +23,7 @@ class DataNotFoundException extends DbException
* @param string $table
* @param array $config
*/
- public function __construct($message, $table = '', array $config = [])
+ public function __construct(string $message, string $table = '', array $config = [])
{
$this->message = $message;
$this->table = $table;
diff --git a/thinkphp/library/think/exception/DbException.php b/vendor/topthink/think-orm/src/db/exception/DbException.php
old mode 100755
new mode 100644
similarity index 84%
rename from thinkphp/library/think/exception/DbException.php
rename to vendor/topthink/think-orm/src/db/exception/DbException.php
index 0f504257c..f68b21c02
--- a/thinkphp/library/think/exception/DbException.php
+++ b/vendor/topthink/think-orm/src/db/exception/DbException.php
@@ -2,14 +2,15 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿
// +----------------------------------------------------------------------
+declare (strict_types = 1);
-namespace think\exception;
+namespace think\db\exception;
use think\Exception;
@@ -26,7 +27,7 @@ class DbException extends Exception
* @param string $sql
* @param int $code
*/
- public function __construct($message, array $config, $sql, $code = 10500)
+ public function __construct(string $message, array $config = [], string $sql = '', int $code = 10500)
{
$this->message = $message;
$this->code = $code;
@@ -40,5 +41,4 @@ class DbException extends Exception
unset($config['username'], $config['password']);
$this->setData('Database Config', $config);
}
-
}
diff --git a/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php b/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php
new file mode 100644
index 000000000..047e45e9b
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php
@@ -0,0 +1,21 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+namespace think\db\exception;
+
+use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentInterface;
+
+/**
+ * 非法数据异常
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements SimpleCacheInvalidArgumentInterface
+{
+}
diff --git a/vendor/topthink/think-orm/src/db/exception/ModelEventException.php b/vendor/topthink/think-orm/src/db/exception/ModelEventException.php
new file mode 100644
index 000000000..767bc1a9f
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/exception/ModelEventException.php
@@ -0,0 +1,19 @@
+
+// +----------------------------------------------------------------------
+
+namespace think\db\exception;
+
+/**
+ * 模型事件异常
+ */
+class ModelEventException extends DbException
+{
+}
diff --git a/thinkphp/library/think/db/exception/ModelNotFoundException.php b/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php
old mode 100755
new mode 100644
similarity index 85%
rename from thinkphp/library/think/db/exception/ModelNotFoundException.php
rename to vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php
index ae52baf3b..84a152579
--- a/thinkphp/library/think/db/exception/ModelNotFoundException.php
+++ b/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php
@@ -2,17 +2,16 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\db\exception;
-use think\exception\DbException;
-
class ModelNotFoundException extends DbException
{
protected $model;
@@ -24,7 +23,7 @@ class ModelNotFoundException extends DbException
* @param string $model
* @param array $config
*/
- public function __construct($message, $model = '', array $config = [])
+ public function __construct(string $message, string $model = '', array $config = [])
{
$this->message = $message;
$this->model = $model;
diff --git a/thinkphp/library/think/exception/PDOException.php b/vendor/topthink/think-orm/src/db/exception/PDOException.php
old mode 100755
new mode 100644
similarity index 60%
rename from thinkphp/library/think/exception/PDOException.php
rename to vendor/topthink/think-orm/src/db/exception/PDOException.php
index 25240b680..efe78b961
--- a/thinkphp/library/think/exception/PDOException.php
+++ b/vendor/topthink/think-orm/src/db/exception/PDOException.php
@@ -2,14 +2,15 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿
// +----------------------------------------------------------------------
+declare (strict_types = 1);
-namespace think\exception;
+namespace think\db\exception;
/**
* PDO异常处理类
@@ -25,16 +26,19 @@ class PDOException extends DbException
* @param string $sql
* @param int $code
*/
- public function __construct(\PDOException $exception, array $config, $sql, $code = 10501)
+ public function __construct(\PDOException $exception, array $config = [], string $sql = '', int $code = 10501)
{
- $error = $exception->errorInfo;
+ $error = $exception->errorInfo;
+ $message = $exception->getMessage();
- $this->setData('PDO Error Info', [
- 'SQLSTATE' => $error[0],
- 'Driver Error Code' => isset($error[1]) ? $error[1] : 0,
- 'Driver Error Message' => isset($error[2]) ? $error[2] : '',
- ]);
+ if (!empty($error)) {
+ $this->setData('PDO Error Info', [
+ 'SQLSTATE' => $error[0],
+ 'Driver Error Code' => isset($error[1]) ? $error[1] : 0,
+ 'Driver Error Message' => isset($error[2]) ? $error[2] : '',
+ ]);
+ }
- parent::__construct($exception->getMessage(), $config, $sql, $code);
+ parent::__construct($message, $config, $sql, $code);
}
}
diff --git a/thinkphp/library/think/facade/Url.php b/vendor/topthink/think-orm/src/facade/Db.php
old mode 100755
new mode 100644
similarity index 68%
rename from thinkphp/library/think/facade/Url.php
rename to vendor/topthink/think-orm/src/facade/Db.php
index 639591ac8..b0296c69b
--- a/thinkphp/library/think/facade/Url.php
+++ b/vendor/topthink/think-orm/src/facade/Db.php
@@ -2,7 +2,7 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
@@ -14,12 +14,10 @@ namespace think\facade;
use think\Facade;
/**
- * @see \think\Url
- * @mixin \think\Url
- * @method string build(string $url = '', mixed $vars = '', mixed $suffix = true, mixed $domain = false) static URL生成 支持路由反射
- * @method void root(string $root) static 指定当前生成URL地址的root
+ * @see \think\DbManager
+ * @mixin \think\DbManager
*/
-class Url extends Facade
+class Db extends Facade
{
/**
* 获取当前Facade对应类名(或者已经绑定的容器对象标识)
@@ -28,6 +26,6 @@ class Url extends Facade
*/
protected static function getFacadeClass()
{
- return 'url';
+ return 'think\DbManager';
}
}
diff --git a/vendor/topthink/think-orm/src/model/Collection.php b/vendor/topthink/think-orm/src/model/Collection.php
new file mode 100644
index 000000000..f017e328f
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/Collection.php
@@ -0,0 +1,265 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model;
+
+use think\Collection as BaseCollection;
+use think\Model;
+use think\Paginator;
+
+/**
+ * 模型数据集类
+ */
+class Collection extends BaseCollection
+{
+ /**
+ * 延迟预载入关联查询
+ * @access public
+ * @param array|string $relation 关联
+ * @param mixed $cache 关联缓存
+ * @return $this
+ */
+ public function load($relation, $cache = false)
+ {
+ if (!$this->isEmpty()) {
+ $item = current($this->items);
+ $item->eagerlyResultSet($this->items, (array) $relation, [], false, $cache);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 删除数据集的数据
+ * @access public
+ * @return bool
+ */
+ public function delete(): bool
+ {
+ $this->each(function (Model $model) {
+ $model->delete();
+ });
+
+ return true;
+ }
+
+ /**
+ * 更新数据
+ * @access public
+ * @param array $data 数据数组
+ * @param array $allowField 允许字段
+ * @return bool
+ */
+ public function update(array $data, array $allowField = []): bool
+ {
+ $this->each(function (Model $model) use ($data, $allowField) {
+ if (!empty($allowField)) {
+ $model->allowField($allowField);
+ }
+
+ $model->save($data);
+ });
+
+ return true;
+ }
+
+ /**
+ * 设置需要隐藏的输出属性
+ * @access public
+ * @param array $hidden 属性列表
+ * @return $this
+ */
+ public function hidden(array $hidden)
+ {
+ $this->each(function (Model $model) use ($hidden) {
+ $model->hidden($hidden);
+ });
+
+ return $this;
+ }
+
+ /**
+ * 设置需要输出的属性
+ * @access public
+ * @param array $visible
+ * @return $this
+ */
+ public function visible(array $visible)
+ {
+ $this->each(function (Model $model) use ($visible) {
+ $model->visible($visible);
+ });
+
+ return $this;
+ }
+
+ /**
+ * 设置需要追加的输出属性
+ * @access public
+ * @param array $append 属性列表
+ * @return $this
+ */
+ public function append(array $append)
+ {
+ $this->each(function (Model $model) use ($append) {
+ $model->append($append);
+ });
+
+ return $this;
+ }
+
+ /**
+ * 设置模型输出场景
+ * @access public
+ * @param string $scene 场景名称
+ * @return $this
+ */
+ public function scene(string $scene)
+ {
+ $this->each(function (Model $model) use ($scene) {
+ $model->scene($scene);
+ });
+
+ return $this;
+ }
+
+ /**
+ * 设置父模型
+ * @access public
+ * @param Model $parent 父模型
+ * @return $this
+ */
+ public function setParent(Model $parent)
+ {
+ $this->each(function (Model $model) use ($parent) {
+ $model->setParent($parent);
+ });
+
+ return $this;
+ }
+
+ /**
+ * 设置数据字段获取器
+ * @access public
+ * @param string|array $name 字段名
+ * @param callable $callback 闭包获取器
+ * @return $this
+ */
+ public function withAttr($name, $callback = null)
+ {
+ $this->each(function (Model $model) use ($name, $callback) {
+ $model->withAttribute($name, $callback);
+ });
+
+ return $this;
+ }
+
+ /**
+ * 绑定(一对一)关联属性到当前模型
+ * @access protected
+ * @param string $relation 关联名称
+ * @param array $attrs 绑定属性
+ * @return $this
+ * @throws Exception
+ */
+ public function bindAttr(string $relation, array $attrs = [])
+ {
+ $this->each(function (Model $model) use ($relation, $attrs) {
+ $model->bindAttr($relation, $attrs);
+ });
+
+ return $this;
+ }
+
+ /**
+ * 按指定键整理数据
+ *
+ * @access public
+ * @param mixed $items 数据
+ * @param string $indexKey 键名
+ * @return array
+ */
+ public function dictionary($items = null, string &$indexKey = null)
+ {
+ if ($items instanceof self || $items instanceof Paginator) {
+ $items = $items->all();
+ }
+
+ $items = is_null($items) ? $this->items : $items;
+
+ if ($items && empty($indexKey)) {
+ $indexKey = $items[0]->getPk();
+ }
+
+ if (isset($indexKey) && is_string($indexKey)) {
+ return array_column($items, null, $indexKey);
+ }
+
+ return $items;
+ }
+
+ /**
+ * 比较数据集,返回差集
+ *
+ * @access public
+ * @param mixed $items 数据
+ * @param string $indexKey 指定比较的键名
+ * @return static
+ */
+ public function diff($items, string $indexKey = null)
+ {
+ if ($this->isEmpty()) {
+ return new static($items);
+ }
+
+ $diff = [];
+ $dictionary = $this->dictionary($items, $indexKey);
+
+ if (is_string($indexKey)) {
+ foreach ($this->items as $item) {
+ if (!isset($dictionary[$item[$indexKey]])) {
+ $diff[] = $item;
+ }
+ }
+ }
+
+ return new static($diff);
+ }
+
+ /**
+ * 比较数据集,返回交集
+ *
+ * @access public
+ * @param mixed $items 数据
+ * @param string $indexKey 指定比较的键名
+ * @return static
+ */
+ public function intersect($items, string $indexKey = null)
+ {
+ if ($this->isEmpty()) {
+ return new static([]);
+ }
+
+ $intersect = [];
+ $dictionary = $this->dictionary($items, $indexKey);
+
+ if (is_string($indexKey)) {
+ foreach ($this->items as $item) {
+ if (isset($dictionary[$item[$indexKey]])) {
+ $intersect[] = $item;
+ }
+ }
+ }
+
+ return new static($intersect);
+ }
+}
diff --git a/thinkphp/library/think/model/Pivot.php b/vendor/topthink/think-orm/src/model/Pivot.php
old mode 100755
new mode 100644
similarity index 65%
rename from thinkphp/library/think/model/Pivot.php
rename to vendor/topthink/think-orm/src/model/Pivot.php
index a3a395e3f..893c01b7e
--- a/thinkphp/library/think/model/Pivot.php
+++ b/vendor/topthink/think-orm/src/model/Pivot.php
@@ -2,33 +2,44 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\model;
use think\Model;
+/**
+ * 多对多中间表模型类
+ */
class Pivot extends Model
{
- /** @var Model */
+ /**
+ * 父模型
+ * @var Model
+ */
public $parent;
+ /**
+ * 是否时间自动写入
+ * @var bool
+ */
protected $autoWriteTimestamp = false;
/**
* 架构函数
* @access public
- * @param array|object $data 数据
- * @param Model $parent 上级模型
- * @param string $table 中间数据表名
+ * @param array $data 数据
+ * @param Model $parent 上级模型
+ * @param string $table 中间数据表名
*/
- public function __construct($data = [], Model $parent = null, $table = '')
+ public function __construct(array $data = [], Model $parent = null, string $table = '')
{
$this->parent = $parent;
diff --git a/vendor/topthink/think-orm/src/model/Relation.php b/vendor/topthink/think-orm/src/model/Relation.php
new file mode 100644
index 000000000..e823bd90b
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/Relation.php
@@ -0,0 +1,278 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model;
+
+use Closure;
+use ReflectionFunction;
+use think\db\BaseQuery as Query;
+use think\db\exception\DbException as Exception;
+use think\Model;
+
+/**
+ * 模型关联基础类
+ * @package think\model
+ * @mixin Query
+ */
+abstract class Relation
+{
+ /**
+ * 父模型对象
+ * @var Model
+ */
+ protected $parent;
+
+ /**
+ * 当前关联的模型类名
+ * @var string
+ */
+ protected $model;
+
+ /**
+ * 关联模型查询对象
+ * @var Query
+ */
+ protected $query;
+
+ /**
+ * 关联表外键
+ * @var string
+ */
+ protected $foreignKey;
+
+ /**
+ * 关联表主键
+ * @var string
+ */
+ protected $localKey;
+
+ /**
+ * 是否执行关联基础查询
+ * @var bool
+ */
+ protected $baseQuery;
+
+ /**
+ * 是否为自关联
+ * @var bool
+ */
+ protected $selfRelation = false;
+
+ /**
+ * 关联数据数量限制
+ * @var int
+ */
+ protected $withLimit;
+
+ /**
+ * 关联数据字段限制
+ * @var array
+ */
+ protected $withField;
+
+ /**
+ * 获取关联的所属模型
+ * @access public
+ * @return Model
+ */
+ public function getParent(): Model
+ {
+ return $this->parent;
+ }
+
+ /**
+ * 获取当前的关联模型类的Query实例
+ * @access public
+ * @return Query
+ */
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ /**
+ * 获取关联表外键
+ * @access public
+ * @return string
+ */
+ public function getForeignKey()
+ {
+ return $this->foreignKey;
+ }
+
+ /**
+ * 获取关联表主键
+ * @access public
+ * @return string
+ */
+ public function getLocalKey()
+ {
+ return $this->localKey;
+ }
+
+ /**
+ * 获取当前的关联模型类的实例
+ * @access public
+ * @return Model
+ */
+ public function getModel(): Model
+ {
+ return $this->query->getModel();
+ }
+
+ /**
+ * 当前关联是否为自关联
+ * @access public
+ * @return bool
+ */
+ public function isSelfRelation(): bool
+ {
+ return $this->selfRelation;
+ }
+
+ /**
+ * 封装关联数据集
+ * @access public
+ * @param array $resultSet 数据集
+ * @param Model $parent 父模型
+ * @return mixed
+ */
+ protected function resultSetBuild(array $resultSet, Model $parent = null)
+ {
+ return (new $this->model)->toCollection($resultSet)->setParent($parent);
+ }
+
+ protected function getQueryFields(string $model)
+ {
+ $fields = $this->query->getOptions('field');
+ return $this->getRelationQueryFields($fields, $model);
+ }
+
+ protected function getRelationQueryFields($fields, string $model)
+ {
+ if (empty($fields) || '*' == $fields) {
+ return $model . '.*';
+ }
+
+ if (is_string($fields)) {
+ $fields = explode(',', $fields);
+ }
+
+ foreach ($fields as &$field) {
+ if (false === strpos($field, '.')) {
+ $field = $model . '.' . $field;
+ }
+ }
+
+ return $fields;
+ }
+
+ protected function getQueryWhere(array &$where, string $relation): void
+ {
+ foreach ($where as $key => &$val) {
+ if (is_string($key)) {
+ $where[] = [false === strpos($key, '.') ? $relation . '.' . $key : $key, '=', $val];
+ unset($where[$key]);
+ } elseif (isset($val[0]) && false === strpos($val[0], '.')) {
+ $val[0] = $relation . '.' . $val[0];
+ }
+ }
+ }
+
+ /**
+ * 更新数据
+ * @access public
+ * @param array $data 更新数据
+ * @return integer
+ */
+ public function update(array $data = []): int
+ {
+ return $this->query->update($data);
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param mixed $data 表达式 true 表示强制删除
+ * @return int
+ * @throws Exception
+ * @throws PDOException
+ */
+ public function delete($data = null): int
+ {
+ return $this->query->delete($data);
+ }
+
+ /**
+ * 限制关联数据的数量
+ * @access public
+ * @param int $limit 关联数量限制
+ * @return $this
+ */
+ public function withLimit(int $limit)
+ {
+ $this->withLimit = $limit;
+ return $this;
+ }
+
+ /**
+ * 限制关联数据的字段
+ * @access public
+ * @param array $field 关联字段限制
+ * @return $this
+ */
+ public function withField(array $field)
+ {
+ $this->withField = $field;
+ return $this;
+ }
+
+ /**
+ * 判断闭包的参数类型
+ * @access protected
+ * @return mixed
+ */
+ protected function getClosureType(Closure $closure)
+ {
+ $reflect = new ReflectionFunction($closure);
+ $params = $reflect->getParameters();
+
+ if (!empty($params)) {
+ $type = $params[0]->getType();
+ return is_null($type) || Relation::class == $type->getName() ? $this : $this->query;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 执行基础查询(仅执行一次)
+ * @access protected
+ * @return void
+ */
+ protected function baseQuery(): void
+ {}
+
+ public function __call($method, $args)
+ {
+ if ($this->query) {
+ // 执行基础查询
+ $this->baseQuery();
+
+ $result = call_user_func_array([$this->query, $method], $args);
+
+ return $result === $this->query ? $this : $result;
+ }
+
+ throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
+ }
+}
diff --git a/thinkphp/library/think/model/concern/Attribute.php b/vendor/topthink/think-orm/src/model/concern/Attribute.php
old mode 100755
new mode 100644
similarity index 53%
rename from thinkphp/library/think/model/concern/Attribute.php
rename to vendor/topthink/think-orm/src/model/concern/Attribute.php
index b7a80fd3b..8f6f918de
--- a/thinkphp/library/think/model/concern/Attribute.php
+++ b/vendor/topthink/think-orm/src/model/concern/Attribute.php
@@ -2,20 +2,24 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\model\concern;
use InvalidArgumentException;
-use think\Exception;
-use think\Loader;
+use think\db\Raw;
+use think\helper\Str;
use think\model\Relation;
+/**
+ * 模型数据处理
+ */
trait Attribute
{
/**
@@ -28,25 +32,19 @@ trait Attribute
* 数据表字段信息 留空则自动获取
* @var array
*/
+ protected $schema = [];
+
+ /**
+ * 当前允许写入的字段
+ * @var array
+ */
protected $field = [];
/**
- * JSON数据表字段
+ * 字段自动类型转换
* @var array
*/
- protected $json = [];
-
- /**
- * JSON数据取出是否需要转换为数组
- * @var bool
- */
- protected $jsonAssoc = false;
-
- /**
- * JSON数据表字段类型
- * @var array
- */
- protected $jsonType = [];
+ protected $type = [];
/**
* 数据表废弃字段
@@ -60,30 +58,48 @@ trait Attribute
*/
protected $readonly = [];
- /**
- * 数据表字段类型
- * @var array
- */
- protected $type = [];
-
/**
* 当前模型数据
* @var array
*/
private $data = [];
- /**
- * 修改器执行记录
- * @var array
- */
- private $set = [];
-
/**
* 原始数据
* @var array
*/
private $origin = [];
+ /**
+ * JSON数据表字段
+ * @var array
+ */
+ protected $json = [];
+
+ /**
+ * JSON数据表字段类型
+ * @var array
+ */
+ protected $jsonType = [];
+
+ /**
+ * JSON数据取出是否需要转换为数组
+ * @var bool
+ */
+ protected $jsonAssoc = false;
+
+ /**
+ * 是否严格字段大小写
+ * @var bool
+ */
+ protected $strict = true;
+
+ /**
+ * 获取器数据
+ * @var array
+ */
+ private $get = [];
+
/**
* 动态获取器
* @var array
@@ -106,9 +122,10 @@ trait Attribute
* @param string $key 名称
* @return bool
*/
- protected function isPk($key)
+ protected function isPk(string $key): bool
{
$pk = $this->getPk();
+
if (is_string($pk) && $pk == $key) {
return true;
} elseif (is_array($pk) && in_array($key, $pk)) {
@@ -121,11 +138,12 @@ trait Attribute
/**
* 获取模型对象的主键值
* @access public
- * @return integer
+ * @return mixed
*/
public function getKey()
{
$pk = $this->getPk();
+
if (is_string($pk) && array_key_exists($pk, $this->data)) {
return $this->data[$pk];
}
@@ -136,15 +154,11 @@ trait Attribute
/**
* 设置允许写入的字段
* @access public
- * @param array|string|true $field 允许写入的字段 如果为true只允许写入数据表字段
+ * @param array $field 允许写入的字段
* @return $this
*/
- public function allowField($field)
+ public function allowField(array $field)
{
- if (is_string($field)) {
- $field = explode(',', $field);
- }
-
$this->field = $field;
return $this;
@@ -153,61 +167,64 @@ trait Attribute
/**
* 设置只读字段
* @access public
- * @param array|string $field 只读字段
+ * @param array $field 只读字段
* @return $this
*/
- public function readonly($field)
+ public function readOnly(array $field)
{
- if (is_string($field)) {
- $field = explode(',', $field);
- }
-
$this->readonly = $field;
return $this;
}
/**
- * 设置数据对象值
- * @access public
- * @param mixed $data 数据或者属性名
- * @param mixed $value 值
- * @return $this
+ * 获取实际的字段名
+ * @access protected
+ * @param string $name 字段名
+ * @return string
*/
- public function data($data, $value = null)
+ protected function getRealFieldName(string $name): string
{
- if (is_string($data)) {
- $this->data[$data] = $value;
- return $this;
+ if ($this->convertNameToCamel || !$this->strict) {
+ return Str::snake($name);
}
+ return $name;
+ }
+
+ /**
+ * 设置数据对象值
+ * @access public
+ * @param array $data 数据
+ * @param bool $set 是否调用修改器
+ * @param array $allow 允许的字段名
+ * @return $this
+ */
+ public function data(array $data, bool $set = false, array $allow = [])
+ {
// 清空数据
$this->data = [];
- if (is_object($data)) {
- $data = get_object_vars($data);
- }
-
- if ($this->disuse) {
- // 废弃字段
- foreach ((array) $this->disuse as $key) {
- if (array_key_exists($key, $data)) {
- unset($data[$key]);
- }
+ // 废弃字段
+ foreach ($this->disuse as $key) {
+ if (array_key_exists($key, $data)) {
+ unset($data[$key]);
}
}
- if (true === $value) {
- // 数据对象赋值
- foreach ($data as $key => $value) {
- $this->setAttr($key, $value, $data);
- }
- } elseif (is_array($value)) {
- foreach ($value as $name) {
+ if (!empty($allow)) {
+ $result = [];
+ foreach ($allow as $name) {
if (isset($data[$name])) {
- $this->data[$name] = $data[$name];
+ $result[$name] = $data[$name];
}
}
+ $data = $result;
+ }
+
+ if ($set) {
+ // 数据对象赋值
+ $this->setAttrs($data);
} else {
$this->data = $data;
}
@@ -216,24 +233,17 @@ trait Attribute
}
/**
- * 批量设置数据对象值
+ * 批量追加数据对象值
* @access public
- * @param mixed $data 数据
+ * @param array $data 数据
* @param bool $set 是否需要进行数据处理
* @return $this
*/
- public function appendData($data, $set = false)
+ public function appendData(array $data, bool $set = false)
{
if ($set) {
- // 进行数据处理
- foreach ($data as $key => $value) {
- $this->setAttr($key, $value, $data);
- }
+ $this->setAttrs($data);
} else {
- if (is_object($data)) {
- $data = get_object_vars($data);
- }
-
$this->data = array_merge($this->data, $data);
}
@@ -246,30 +256,38 @@ trait Attribute
* @param string $name 字段名 留空获取全部
* @return mixed
*/
- public function getOrigin($name = null)
+ public function getOrigin(string $name = null)
{
if (is_null($name)) {
return $this->origin;
}
- return array_key_exists($name, $this->origin) ? $this->origin[$name] : null;
+
+ $fieldName = $this->getRealFieldName($name);
+
+ return array_key_exists($fieldName, $this->origin) ? $this->origin[$fieldName] : null;
}
/**
- * 获取对象原始数据 如果不存在指定字段返回false
+ * 获取当前对象数据 如果不存在指定字段返回false
* @access public
* @param string $name 字段名 留空获取全部
* @return mixed
* @throws InvalidArgumentException
*/
- public function getData($name = null)
+ public function getData(string $name = null)
{
if (is_null($name)) {
return $this->data;
- } elseif (array_key_exists($name, $this->data)) {
- return $this->data[$name];
- } elseif (array_key_exists($name, $this->relation)) {
- return $this->relation[$name];
}
+
+ $fieldName = $this->getRealFieldName($name);
+
+ if (array_key_exists($fieldName, $this->data)) {
+ return $this->data[$fieldName];
+ } elseif (array_key_exists($fieldName, $this->relation)) {
+ return $this->relation[$fieldName];
+ }
+
throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
}
@@ -278,26 +296,20 @@ trait Attribute
* @access public
* @return array
*/
- public function getChangedData()
+ public function getChangedData(): array
{
- if ($this->force) {
- $data = $this->data;
- } else {
- $data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) {
- if ((empty($a) || empty($b)) && $a !== $b) {
- return 1;
- }
+ $data = $this->force ? $this->data : array_udiff_assoc($this->data, $this->origin, function ($a, $b) {
+ if ((empty($a) || empty($b)) && $a !== $b) {
+ return 1;
+ }
- return is_object($a) || $a != $b ? 1 : 0;
- });
- }
+ return is_object($a) || $a != $b ? 1 : 0;
+ });
- if (!empty($this->readonly)) {
- // 只读字段不允许更新
- foreach ($this->readonly as $key => $field) {
- if (isset($data[$field])) {
- unset($data[$field]);
- }
+ // 只读字段不允许更新
+ foreach ($this->readonly as $key => $field) {
+ if (array_key_exists($field, $data)) {
+ unset($data[$field]);
}
}
@@ -305,93 +317,65 @@ trait Attribute
}
/**
- * 修改器 设置数据对象值
+ * 直接设置数据对象值
+ * @access public
+ * @param string $name 属性名
+ * @param mixed $value 值
+ * @return void
+ */
+ public function set(string $name, $value): void
+ {
+ $name = $this->getRealFieldName($name);
+
+ $this->data[$name] = $value;
+ unset($this->get[$name]);
+ }
+
+ /**
+ * 通过修改器 批量设置数据对象值
+ * @access public
+ * @param array $data 数据
+ * @return void
+ */
+ public function setAttrs(array $data): void
+ {
+ // 进行数据处理
+ foreach ($data as $key => $value) {
+ $this->setAttr($key, $value, $data);
+ }
+ }
+
+ /**
+ * 通过修改器 设置数据对象值
* @access public
* @param string $name 属性名
* @param mixed $value 属性值
* @param array $data 数据
* @return void
*/
- public function setAttr($name, $value, $data = [])
+ public function setAttr(string $name, $value, array $data = []): void
{
- if (isset($this->set[$name])) {
- return;
- }
+ $name = $this->getRealFieldName($name);
- if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) {
- // 自动写入的时间戳字段
- $value = $this->autoWriteTimestamp($name);
- } else {
- // 检测修改器
- $method = 'set' . Loader::parseName($name, 1) . 'Attr';
+ // 检测修改器
+ $method = 'set' . Str::studly($name) . 'Attr';
- if (method_exists($this, $method)) {
- $value = $this->$method($value, array_merge($this->data, $data));
+ if (method_exists($this, $method)) {
+ $array = $this->data;
- $this->set[$name] = true;
- } elseif (isset($this->type[$name])) {
- // 类型转换
- $value = $this->writeTransform($value, $this->type[$name]);
+ $value = $this->$method($value, array_merge($this->data, $data));
+
+ if (is_null($value) && $array !== $this->data) {
+ return;
}
+ } elseif (isset($this->type[$name])) {
+ // 类型转换
+ $value = $this->writeTransform($value, $this->type[$name]);
}
// 设置数据对象属性
$this->data[$name] = $value;
- }
-
- /**
- * 是否需要自动写入时间字段
- * @access public
- * @param bool $auto
- * @return $this
- */
- public function isAutoWriteTimestamp($auto)
- {
- $this->autoWriteTimestamp = $auto;
-
- return $this;
- }
-
- /**
- * 自动写入时间戳
- * @access protected
- * @param string $name 时间戳字段
- * @return mixed
- */
- protected function autoWriteTimestamp($name)
- {
- if (isset($this->type[$name])) {
- $type = $this->type[$name];
-
- if (strpos($type, ':')) {
- list($type, $param) = explode(':', $type, 2);
- }
-
- switch ($type) {
- case 'datetime':
- case 'date':
- $format = !empty($param) ? $param : $this->dateFormat;
- $format .= strpos($format, 'u') || false !== strpos($format, '\\') ? '' : '.u';
- $value = $this->formatDateTime($format);
- break;
- case 'timestamp':
- case 'integer':
- default:
- $value = time();
- break;
- }
- } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [
- 'datetime',
- 'date',
- 'timestamp',
- ])) {
- $format = strpos($this->dateFormat, 'u') || false !== strpos($this->dateFormat, '\\') ? '' : '.u';
- $value = $this->formatDateTime($this->dateFormat . $format);
- } else {
- $value = time();
- }
-
- return $value;
+ unset($this->get[$name]);
}
/**
@@ -407,10 +391,14 @@ trait Attribute
return;
}
+ if ($value instanceof Raw) {
+ return $value;
+ }
+
if (is_array($type)) {
- list($type, $param) = $type;
+ [$type, $param] = $type;
} elseif (strpos($type, ':')) {
- list($type, $param) = explode(':', $type, 2);
+ [$type, $param] = explode(':', $type, 2);
}
switch ($type) {
@@ -421,7 +409,7 @@ trait Attribute
if (empty($param)) {
$value = (float) $value;
} else {
- $value = (float) number_format($value, $param, '.', '');
+ $value = (float) number_format($value, (int) $param, '.', '');
}
break;
case 'boolean':
@@ -433,9 +421,8 @@ trait Attribute
}
break;
case 'datetime':
- $format = !empty($param) ? $param : $this->dateFormat;
- $value = is_numeric($value) ? $value : strtotime($value);
- $value = $this->formatDateTime($format, $value);
+ $value = is_numeric($value) ? $value : strtotime($value);
+ $value = $this->formatDateTime('Y-m-d H:i:s.u', $value, true);
break;
case 'object':
if (is_object($value)) {
@@ -451,6 +438,11 @@ trait Attribute
case 'serialize':
$value = serialize($value);
break;
+ default:
+ if (is_object($value) && false !== strpos($type, '\\') && method_exists($value, '__toString')) {
+ // 对象类型
+ $value = $value->__toString();
+ }
}
return $value;
@@ -460,54 +452,89 @@ trait Attribute
* 获取器 获取数据对象的值
* @access public
* @param string $name 名称
- * @param array $item 数据
* @return mixed
* @throws InvalidArgumentException
*/
- public function getAttr($name, &$item = null)
+ public function getAttr(string $name)
{
try {
- $notFound = false;
+ $relation = false;
$value = $this->getData($name);
} catch (InvalidArgumentException $e) {
- $notFound = true;
+ $relation = $this->isRelationAttr($name);
$value = null;
}
- // 检测属性获取器
- $fieldName = Loader::parseName($name);
- $method = 'get' . Loader::parseName($name, 1) . 'Attr';
+ return $this->getValue($name, $value, $relation);
+ }
+ /**
+ * 获取经过获取器处理后的数据对象的值
+ * @access protected
+ * @param string $name 字段名称
+ * @param mixed $value 字段值
+ * @param bool|string $relation 是否为关联属性或者关联名
+ * @return mixed
+ * @throws InvalidArgumentException
+ */
+ protected function getValue(string $name, $value, $relation = false)
+ {
+ // 检测属性获取器
+ $fieldName = $this->getRealFieldName($name);
+
+ if (array_key_exists($fieldName, $this->get)) {
+ return $this->get[$fieldName];
+ }
+
+ $method = 'get' . Str::studly($name) . 'Attr';
if (isset($this->withAttr[$fieldName])) {
- if ($notFound && $relation = $this->isRelationAttr($name)) {
- $modelRelation = $this->$relation();
- $value = $this->getRelationData($modelRelation);
+ if ($relation) {
+ $value = $this->getRelationValue($relation);
}
- $closure = $this->withAttr[$fieldName];
- $value = $closure($value, $this->data);
+ if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) {
+ $value = $this->getJsonValue($fieldName, $value);
+ } else {
+ $closure = $this->withAttr[$fieldName];
+ $value = $closure($value, $this->data);
+ }
} elseif (method_exists($this, $method)) {
- if ($notFound && $relation = $this->isRelationAttr($name)) {
- $modelRelation = $this->$relation();
- $value = $this->getRelationData($modelRelation);
+ if ($relation) {
+ $value = $this->getRelationValue($relation);
}
$value = $this->$method($value, $this->data);
- } elseif (isset($this->type[$name])) {
+ } elseif (isset($this->type[$fieldName])) {
// 类型转换
- $value = $this->readTransform($value, $this->type[$name]);
- } elseif ($this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) {
- if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [
- 'datetime',
- 'date',
- 'timestamp',
- ])) {
- $value = $this->formatDateTime($this->dateFormat, $value);
+ $value = $this->readTransform($value, $this->type[$fieldName]);
+ } elseif ($this->autoWriteTimestamp && in_array($fieldName, [$this->createTime, $this->updateTime])) {
+ $value = $this->getTimestampValue($value);
+ } elseif ($relation) {
+ $value = $this->getRelationValue($relation);
+ // 保存关联对象值
+ $this->relation[$name] = $value;
+ }
+
+ $this->get[$fieldName] = $value;
+
+ return $value;
+ }
+
+ /**
+ * 获取JSON字段属性值
+ * @access protected
+ * @param string $name 属性名
+ * @param mixed $value JSON数据
+ * @return mixed
+ */
+ protected function getJsonValue($name, $value)
+ {
+ foreach ($this->withAttr[$name] as $key => $closure) {
+ if ($this->jsonAssoc) {
+ $value[$key] = $closure($value[$key], $value);
} else {
- $value = $this->formatDateTime($this->dateFormat, $value, true);
+ $value->$key = $closure($value->$key, $value);
}
- } elseif ($notFound) {
- $value = $this->getRelationAttribute($name, $item);
}
return $value;
@@ -516,42 +543,14 @@ trait Attribute
/**
* 获取关联属性值
* @access protected
- * @param string $name 属性名
- * @param array $item 数据
+ * @param string $relation 关联名
* @return mixed
*/
- protected function getRelationAttribute($name, &$item)
+ protected function getRelationValue(string $relation)
{
- $relation = $this->isRelationAttr($name);
+ $modelRelation = $this->$relation();
- if ($relation) {
- $modelRelation = $this->$relation();
- if ($modelRelation instanceof Relation) {
- $value = $this->getRelationData($modelRelation);
-
- if ($item && method_exists($modelRelation, 'getBindAttr') && $bindAttr = $modelRelation->getBindAttr()) {
-
- foreach ($bindAttr as $key => $attr) {
- $key = is_numeric($key) ? $attr : $key;
-
- if (isset($item[$key])) {
- throw new Exception('bind attr has exists:' . $key);
- } else {
- $item[$key] = $value ? $value->getAttr($attr) : null;
- }
- }
-
- return false;
- }
-
- // 保存关联对象值
- $this->relation[$name] = $value;
-
- return $value;
- }
- }
-
- throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
+ return $modelRelation instanceof Relation ? $this->getRelationData($modelRelation) : null;
}
/**
@@ -568,9 +567,9 @@ trait Attribute
}
if (is_array($type)) {
- list($type, $param) = $type;
+ [$type, $param] = $type;
} elseif (strpos($type, ':')) {
- list($type, $param) = explode(':', $type, 2);
+ [$type, $param] = explode(':', $type, 2);
}
switch ($type) {
@@ -581,7 +580,7 @@ trait Attribute
if (empty($param)) {
$value = (float) $value;
} else {
- $value = (float) number_format($value, $param, '.', '');
+ $value = (float) number_format($value, (int) $param, '.', '');
}
break;
case 'boolean':
@@ -632,20 +631,25 @@ trait Attribute
* @param callable $callback 闭包获取器
* @return $this
*/
- public function withAttribute($name, $callback = null)
+ public function withAttribute($name, callable $callback = null)
{
if (is_array($name)) {
foreach ($name as $key => $val) {
- $key = Loader::parseName($key);
-
- $this->withAttr[$key] = $val;
+ $this->withAttribute($key, $val);
}
} else {
- $name = Loader::parseName($name);
+ $name = $this->getRealFieldName($name);
- $this->withAttr[$name] = $callback;
+ if (strpos($name, '.')) {
+ [$name, $key] = explode('.', $name);
+
+ $this->withAttr[$name][$key] = $callback;
+ } else {
+ $this->withAttr[$name] = $callback;
+ }
}
return $this;
}
+
}
diff --git a/vendor/topthink/think-orm/src/model/concern/Conversion.php b/vendor/topthink/think-orm/src/model/concern/Conversion.php
new file mode 100644
index 000000000..35d96d050
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/concern/Conversion.php
@@ -0,0 +1,358 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model\concern;
+
+use think\Collection;
+use think\db\exception\DbException as Exception;
+use think\helper\Str;
+use think\Model;
+use think\model\Collection as ModelCollection;
+use think\model\relation\OneToOne;
+
+/**
+ * 模型数据转换处理
+ */
+trait Conversion
+{
+ /**
+ * 数据输出显示的属性
+ * @var array
+ */
+ protected $visible = [];
+
+ /**
+ * 数据输出隐藏的属性
+ * @var array
+ */
+ protected $hidden = [];
+
+ /**
+ * 数据输出需要追加的属性
+ * @var array
+ */
+ protected $append = [];
+
+ /**
+ * 场景
+ * @var array
+ */
+ protected $scene = [];
+
+ /**
+ * 数据输出字段映射
+ * @var array
+ */
+ protected $mapping = [];
+
+ /**
+ * 数据集对象名
+ * @var string
+ */
+ protected $resultSetType;
+
+ /**
+ * 数据命名是否自动转为驼峰
+ * @var bool
+ */
+ protected $convertNameToCamel;
+
+ /**
+ * 转换数据为驼峰命名(用于输出)
+ * @access public
+ * @param bool $toCamel 是否自动驼峰命名
+ * @return $this
+ */
+ public function convertNameToCamel(bool $toCamel = true)
+ {
+ $this->convertNameToCamel = $toCamel;
+ return $this;
+ }
+
+ /**
+ * 设置需要附加的输出属性
+ * @access public
+ * @param array $append 属性列表
+ * @return $this
+ */
+ public function append(array $append = [])
+ {
+ $this->append = $append;
+
+ return $this;
+ }
+
+ /**
+ * 设置输出层场景
+ * @access public
+ * @param string $scene 场景名称
+ * @return $this
+ */
+ public function scene(string $scene)
+ {
+ if (isset($this->scene[$scene])) {
+ $data = $this->scene[$scene];
+ foreach (['append', 'hidden', 'visible'] as $name) {
+ if (isset($data[$name])) {
+ $this->$name($data[$name]);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置附加关联对象的属性
+ * @access public
+ * @param string $attr 关联属性
+ * @param string|array $append 追加属性名
+ * @return $this
+ * @throws Exception
+ */
+ public function appendRelationAttr(string $attr, array $append)
+ {
+ $relation = Str::camel($attr);
+
+ if (isset($this->relation[$relation])) {
+ $model = $this->relation[$relation];
+ } else {
+ $model = $this->getRelationData($this->$relation());
+ }
+
+ if ($model instanceof Model) {
+ foreach ($append as $key => $attr) {
+ $key = is_numeric($key) ? $attr : $key;
+ if (isset($this->data[$key])) {
+ throw new Exception('bind attr has exists:' . $key);
+ }
+
+ $this->data[$key] = $model->$attr;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置需要隐藏的输出属性
+ * @access public
+ * @param array $hidden 属性列表
+ * @return $this
+ */
+ public function hidden(array $hidden = [])
+ {
+ $this->hidden = $hidden;
+
+ return $this;
+ }
+
+ /**
+ * 设置需要输出的属性
+ * @access public
+ * @param array $visible
+ * @return $this
+ */
+ public function visible(array $visible = [])
+ {
+ $this->visible = $visible;
+
+ return $this;
+ }
+
+ /**
+ * 设置属性的映射输出
+ * @access public
+ * @param array $map
+ * @return $this
+ */
+ public function mapping(array $map)
+ {
+ $this->mapping = $map;
+
+ return $this;
+ }
+
+ /**
+ * 转换当前模型对象为数组
+ * @access public
+ * @return array
+ */
+ public function toArray(): array
+ {
+ $item = [];
+ $hasVisible = false;
+
+ foreach ($this->visible as $key => $val) {
+ if (is_string($val)) {
+ if (strpos($val, '.')) {
+ [$relation, $name] = explode('.', $val);
+ $this->visible[$relation][] = $name;
+ } else {
+ $this->visible[$val] = true;
+ $hasVisible = true;
+ }
+ unset($this->visible[$key]);
+ }
+ }
+
+ foreach ($this->hidden as $key => $val) {
+ if (is_string($val)) {
+ if (strpos($val, '.')) {
+ [$relation, $name] = explode('.', $val);
+ $this->hidden[$relation][] = $name;
+ } else {
+ $this->hidden[$val] = true;
+ }
+ unset($this->hidden[$key]);
+ }
+ }
+
+ // 合并关联数据
+ $data = array_merge($this->data, $this->relation);
+
+ foreach ($data as $key => $val) {
+ if ($val instanceof Model || $val instanceof ModelCollection) {
+ // 关联模型对象
+ if (isset($this->visible[$key]) && is_array($this->visible[$key])) {
+ $val->visible($this->visible[$key]);
+ } elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) {
+ $val->hidden($this->hidden[$key]);
+ }
+ // 关联模型对象
+ if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) {
+ $item[$key] = $val->toArray();
+ }
+ } elseif (isset($this->visible[$key])) {
+ $item[$key] = $this->getAttr($key);
+ } elseif (!isset($this->hidden[$key]) && !$hasVisible) {
+ $item[$key] = $this->getAttr($key);
+ }
+
+ if (isset($this->mapping[$key])) {
+ // 检查字段映射
+ $mapName = $this->mapping[$key];
+ $item[$mapName] = $item[$key];
+ unset($item[$key]);
+ }
+ }
+
+ // 追加属性(必须定义获取器)
+ foreach ($this->append as $key => $name) {
+ $this->appendAttrToArray($item, $key, $name);
+ }
+
+ if ($this->convertNameToCamel) {
+ foreach ($item as $key => $val) {
+ $name = Str::camel($key);
+ if ($name !== $key) {
+ $item[$name] = $val;
+ unset($item[$key]);
+ }
+ }
+ }
+
+ return $item;
+ }
+
+ protected function appendAttrToArray(array &$item, $key, $name)
+ {
+ if (is_array($name)) {
+ // 追加关联对象属性
+ $relation = $this->getRelation($key, true);
+ $item[$key] = $relation ? $relation->append($name)
+ ->toArray() : [];
+ } elseif (strpos($name, '.')) {
+ [$key, $attr] = explode('.', $name);
+ // 追加关联对象属性
+ $relation = $this->getRelation($key, true);
+ $item[$key] = $relation ? $relation->append([$attr])
+ ->toArray() : [];
+ } else {
+ $value = $this->getAttr($name);
+ $item[$name] = $value;
+
+ $this->getBindAttrValue($name, $value, $item);
+ }
+ }
+
+ protected function getBindAttrValue(string $name, $value, array &$item = [])
+ {
+ $relation = $this->isRelationAttr($name);
+ if (!$relation) {
+ return false;
+ }
+
+ $modelRelation = $this->$relation();
+
+ if ($modelRelation instanceof OneToOne) {
+ $bindAttr = $modelRelation->getBindAttr();
+
+ if (!empty($bindAttr)) {
+ unset($item[$name]);
+ }
+
+ foreach ($bindAttr as $key => $attr) {
+ $key = is_numeric($key) ? $attr : $key;
+
+ if (isset($item[$key])) {
+ throw new Exception('bind attr has exists:' . $key);
+ }
+
+ $item[$key] = $value ? $value->getAttr($attr) : null;
+ }
+ }
+ }
+
+ /**
+ * 转换当前模型对象为JSON字符串
+ * @access public
+ * @param integer $options json参数
+ * @return string
+ */
+ public function toJson(int $options = JSON_UNESCAPED_UNICODE): string
+ {
+ return json_encode($this->toArray(), $options);
+ }
+
+ public function __toString()
+ {
+ return $this->toJson();
+ }
+
+ // JsonSerializable
+ public function jsonSerialize()
+ {
+ return $this->toArray();
+ }
+
+ /**
+ * 转换数据集为数据集对象
+ * @access public
+ * @param array|Collection $collection 数据集
+ * @param string $resultSetType 数据集类
+ * @return Collection
+ */
+ public function toCollection(iterable $collection = [], string $resultSetType = null): Collection
+ {
+ $resultSetType = $resultSetType ?: $this->resultSetType;
+
+ if ($resultSetType && false !== strpos($resultSetType, '\\')) {
+ $collection = new $resultSetType($collection);
+ } else {
+ $collection = new ModelCollection($collection);
+ }
+
+ return $collection;
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/model/concern/ModelEvent.php b/vendor/topthink/think-orm/src/model/concern/ModelEvent.php
new file mode 100644
index 000000000..f560379eb
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/concern/ModelEvent.php
@@ -0,0 +1,88 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model\concern;
+
+use think\db\exception\ModelEventException;
+use think\helper\Str;
+
+/**
+ * 模型事件处理
+ */
+trait ModelEvent
+{
+
+ /**
+ * Event对象
+ * @var object
+ */
+ protected static $event;
+
+ /**
+ * 是否需要事件响应
+ * @var bool
+ */
+ protected $withEvent = true;
+
+ /**
+ * 设置Event对象
+ * @access public
+ * @param object $event Event对象
+ * @return void
+ */
+ public static function setEvent($event)
+ {
+ self::$event = $event;
+ }
+
+ /**
+ * 当前操作的事件响应
+ * @access protected
+ * @param bool $event 是否需要事件响应
+ * @return $this
+ */
+ public function withEvent(bool $event)
+ {
+ $this->withEvent = $event;
+ return $this;
+ }
+
+ /**
+ * 触发事件
+ * @access protected
+ * @param string $event 事件名
+ * @return bool
+ */
+ protected function trigger(string $event): bool
+ {
+ if (!$this->withEvent) {
+ return true;
+ }
+
+ $call = 'on' . Str::studly($event);
+
+ try {
+ if (method_exists(static::class, $call)) {
+ $result = call_user_func([static::class, $call], $this);
+ } elseif (is_object(self::$event) && method_exists(self::$event, 'trigger')) {
+ $result = self::$event->trigger(static::class . '.' . $event, $this);
+ $result = empty($result) ? true : end($result);
+ } else {
+ $result = true;
+ }
+
+ return false === $result ? false : true;
+ } catch (ModelEventException $e) {
+ return false;
+ }
+ }
+}
diff --git a/vendor/topthink/think-orm/src/model/concern/OptimLock.php b/vendor/topthink/think-orm/src/model/concern/OptimLock.php
new file mode 100644
index 000000000..5e6131833
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/concern/OptimLock.php
@@ -0,0 +1,85 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model\concern;
+
+use think\db\exception\DbException as Exception;
+
+/**
+ * 乐观锁
+ */
+trait OptimLock
+{
+ protected function getOptimLockField()
+ {
+ return property_exists($this, 'optimLock') && isset($this->optimLock) ? $this->optimLock : 'lock_version';
+ }
+
+ /**
+ * 数据检查
+ * @access protected
+ * @return void
+ */
+ protected function checkData(): void
+ {
+ $this->isExists() ? $this->updateLockVersion() : $this->recordLockVersion();
+ }
+
+ /**
+ * 记录乐观锁
+ * @access protected
+ * @return void
+ */
+ protected function recordLockVersion(): void
+ {
+ $optimLock = $this->getOptimLockField();
+
+ if ($optimLock) {
+ $this->set($optimLock, 0);
+ }
+ }
+
+ /**
+ * 更新乐观锁
+ * @access protected
+ * @return void
+ */
+ protected function updateLockVersion(): void
+ {
+ $optimLock = $this->getOptimLockField();
+
+ if ($optimLock && $lockVer = $this->getOrigin($optimLock)) {
+ // 更新乐观锁
+ $this->set($optimLock, $lockVer + 1);
+ }
+ }
+
+ public function getWhere()
+ {
+ $where = parent::getWhere();
+ $optimLock = $this->getOptimLockField();
+
+ if ($optimLock && $lockVer = $this->getOrigin($optimLock)) {
+ $where[] = [$optimLock, '=', $lockVer];
+ }
+
+ return $where;
+ }
+
+ protected function checkResult($result): void
+ {
+ if (!$result) {
+ throw new Exception('record has update');
+ }
+ }
+
+}
diff --git a/thinkphp/library/think/model/concern/RelationShip.php b/vendor/topthink/think-orm/src/model/concern/RelationShip.php
old mode 100755
new mode 100644
similarity index 50%
rename from thinkphp/library/think/model/concern/RelationShip.php
rename to vendor/topthink/think-orm/src/model/concern/RelationShip.php
index 38ad5d20f..33c1b890e
--- a/thinkphp/library/think/model/concern/RelationShip.php
+++ b/vendor/topthink/think-orm/src/model/concern/RelationShip.php
@@ -2,18 +2,21 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\model\concern;
+use Closure;
use think\Collection;
-use think\db\Query;
-use think\Loader;
+use think\db\BaseQuery as Query;
+use think\db\exception\DbException as Exception;
+use think\helper\Str;
use think\Model;
use think\model\Relation;
use think\model\relation\BelongsTo;
@@ -21,9 +24,12 @@ use think\model\relation\BelongsToMany;
use think\model\relation\HasMany;
use think\model\relation\HasManyThrough;
use think\model\relation\HasOne;
+use think\model\relation\HasOneThrough;
use think\model\relation\MorphMany;
use think\model\relation\MorphOne;
use think\model\relation\MorphTo;
+use think\model\relation\MorphToMany;
+use think\model\relation\OneToOne;
/**
* 模型关联处理
@@ -46,13 +52,13 @@ trait RelationShip
* 关联写入定义信息
* @var array
*/
- private $together;
+ private $together = [];
/**
* 关联自动写入信息
* @var array
*/
- protected $relationWrite;
+ protected $relationWrite = [];
/**
* 设置父关联对象
@@ -60,7 +66,7 @@ trait RelationShip
* @param Model $model 模型对象
* @return $this
*/
- public function setParent($model)
+ public function setParent(Model $model)
{
$this->parent = $model;
@@ -72,7 +78,7 @@ trait RelationShip
* @access public
* @return Model
*/
- public function getParent()
+ public function getParent(): Model
{
return $this->parent;
}
@@ -81,16 +87,21 @@ trait RelationShip
* 获取当前模型的关联模型数据
* @access public
* @param string $name 关联方法名
+ * @param bool $auto 不存在是否自动获取
* @return mixed
*/
- public function getRelation($name = null)
+ public function getRelation(string $name = null, bool $auto = false)
{
if (is_null($name)) {
return $this->relation;
- } elseif (array_key_exists($name, $this->relation)) {
- return $this->relation[$name];
}
- return;
+
+ if (array_key_exists($name, $this->relation)) {
+ return $this->relation[$name];
+ } elseif ($auto) {
+ $relation = Str::camel($name);
+ return $this->getRelationValue($relation);
+ }
}
/**
@@ -101,32 +112,67 @@ trait RelationShip
* @param array $data 数据
* @return $this
*/
- public function setRelation($name, $value, $data = [])
+ public function setRelation(string $name, $value, array $data = [])
{
// 检测修改器
- $method = 'set' . Loader::parseName($name, 1) . 'Attr';
+ $method = 'set' . Str::studly($name) . 'Attr';
if (method_exists($this, $method)) {
$value = $this->$method($value, array_merge($this->data, $data));
}
- $this->relation[$name] = $value;
+ $this->relation[$this->getRealFieldName($name)] = $value;
return $this;
}
+ /**
+ * 查询当前模型的关联数据
+ * @access public
+ * @param array $relations 关联名
+ * @param array $withRelationAttr 关联获取器
+ * @return void
+ */
+ public function relationQuery(array $relations, array $withRelationAttr = []): void
+ {
+ foreach ($relations as $key => $relation) {
+ $subRelation = [];
+ $closure = null;
+
+ if ($relation instanceof Closure) {
+ // 支持闭包查询过滤关联条件
+ $closure = $relation;
+ $relation = $key;
+ }
+
+ if (is_array($relation)) {
+ $subRelation = $relation;
+ $relation = $key;
+ } elseif (strpos($relation, '.')) {
+ [$relation, $subRelation] = explode('.', $relation, 2);
+ }
+
+ $method = Str::camel($relation);
+ $relationName = Str::snake($relation);
+
+ $relationResult = $this->$method();
+
+ if (isset($withRelationAttr[$relationName])) {
+ $relationResult->withAttr($withRelationAttr[$relationName]);
+ }
+
+ $this->relation[$relation] = $relationResult->getRelation((array) $subRelation, $closure);
+ }
+ }
+
/**
* 关联数据写入
* @access public
- * @param array|string $relation 关联
+ * @param array $relation 关联
* @return $this
*/
- public function together($relation)
+ public function together(array $relation)
{
- if (is_string($relation)) {
- $relation = explode(',', $relation);
- }
-
$this->together = $relation;
$this->checkAutoRelationWrite();
@@ -142,17 +188,14 @@ trait RelationShip
* @param integer $count 个数
* @param string $id 关联表的统计字段
* @param string $joinType JOIN类型
+ * @param Query $query Query对象
* @return Query
*/
- public static function has($relation, $operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
+ public static function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query
{
- $relation = (new static())->$relation();
-
- if (is_array($operator) || $operator instanceof \Closure) {
- return $relation->hasWhere($operator);
- }
-
- return $relation->has($operator, $count, $id, $joinType);
+ return (new static())
+ ->$relation()
+ ->has($operator, $count, $id, $joinType, $query);
}
/**
@@ -161,76 +204,58 @@ trait RelationShip
* @param string $relation 关联方法名
* @param mixed $where 查询条件(数组或者闭包)
* @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
* @return Query
*/
- public static function hasWhere($relation, $where = [], $fields = '*')
+ public static function hasWhere(string $relation, $where = [], string $fields = '*', string $joinType = '', Query $query = null): Query
{
- return (new static())->$relation()->hasWhere($where, $fields);
+ return (new static())
+ ->$relation()
+ ->hasWhere($where, $fields, $joinType, $query);
}
/**
- * 查询当前模型的关联数据
+ * 预载入关联查询 JOIN方式
* @access public
- * @param string|array $relations 关联名
- * @param array $withRelationAttr 关联获取器
- * @return $this
+ * @param Query $query Query对象
+ * @param string $relation 关联方法名
+ * @param mixed $field 字段
+ * @param string $joinType JOIN类型
+ * @param Closure $closure 闭包
+ * @param bool $first
+ * @return bool
*/
- public function relationQuery($relations, $withRelationAttr = [])
+ public function eagerly(Query $query, string $relation, $field, string $joinType = '', Closure $closure = null, bool $first = false): bool
{
- if (is_string($relations)) {
- $relations = explode(',', $relations);
+ $relation = Str::camel($relation);
+ $class = $this->$relation();
+
+ if ($class instanceof OneToOne) {
+ $class->eagerly($query, $relation, $field, $joinType, $closure, $first);
+ return true;
+ } else {
+ return false;
}
-
- foreach ($relations as $key => $relation) {
- $subRelation = '';
- $closure = null;
-
- if ($relation instanceof \Closure) {
- // 支持闭包查询过滤关联条件
- $closure = $relation;
- $relation = $key;
- }
-
- if (is_array($relation)) {
- $subRelation = $relation;
- $relation = $key;
- } elseif (strpos($relation, '.')) {
- list($relation, $subRelation) = explode('.', $relation, 2);
- }
-
- $method = Loader::parseName($relation, 1, false);
- $relationName = Loader::parseName($relation);
-
- $relationResult = $this->$method();
-
- if (isset($withRelationAttr[$relationName])) {
- $relationResult->withAttr($withRelationAttr[$relationName]);
- }
-
- $this->relation[$relation] = $relationResult->getRelation($subRelation, $closure);
- }
-
- return $this;
}
/**
* 预载入关联查询 返回数据集
* @access public
- * @param array $resultSet 数据集
- * @param string $relation 关联名
+ * @param array $resultSet 数据集
+ * @param string $relation 关联名
* @param array $withRelationAttr 关联获取器
- * @param bool $join 是否为JOIN方式
- * @return array
+ * @param bool $join 是否为JOIN方式
+ * @param mixed $cache 关联缓存
+ * @return void
*/
- public function eagerlyResultSet(&$resultSet, $relation, $withRelationAttr = [], $join = false)
+ public function eagerlyResultSet(array &$resultSet, array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void
{
- $relations = is_string($relation) ? explode(',', $relation) : $relation;
-
foreach ($relations as $key => $relation) {
- $subRelation = '';
+ $subRelation = [];
$closure = null;
- if ($relation instanceof \Closure) {
+ if ($relation instanceof Closure) {
$closure = $relation;
$relation = $key;
}
@@ -239,11 +264,13 @@ trait RelationShip
$subRelation = $relation;
$relation = $key;
} elseif (strpos($relation, '.')) {
- list($relation, $subRelation) = explode('.', $relation, 2);
+ [$relation, $subRelation] = explode('.', $relation, 2);
+
+ $subRelation = [$subRelation];
}
- $relation = Loader::parseName($relation, 1, false);
- $relationName = Loader::parseName($relation);
+ $relationName = $relation;
+ $relation = Str::camel($relation);
$relationResult = $this->$relation();
@@ -251,28 +278,33 @@ trait RelationShip
$relationResult->withAttr($withRelationAttr[$relationName]);
}
- $relationResult->eagerlyResultSet($resultSet, $relation, $subRelation, $closure, $join);
+ if (is_scalar($cache)) {
+ $relationCache = [$cache];
+ } else {
+ $relationCache = $cache[$relationName] ?? $cache;
+ }
+
+ $relationResult->eagerlyResultSet($resultSet, $relationName, $subRelation, $closure, $relationCache, $join);
}
}
/**
* 预载入关联查询 返回模型对象
* @access public
- * @param Model $result 数据对象
- * @param string $relation 关联名
- * @param array $withRelationAttr 关联获取器
- * @param bool $join 是否为JOIN方式
- * @return Model
+ * @param Model $result 数据对象
+ * @param array $relations 关联
+ * @param array $withRelationAttr 关联获取器
+ * @param bool $join 是否为JOIN方式
+ * @param mixed $cache 关联缓存
+ * @return void
*/
- public function eagerlyResult(&$result, $relation, $withRelationAttr = [], $join = false)
+ public function eagerlyResult(Model $result, array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void
{
- $relations = is_string($relation) ? explode(',', $relation) : $relation;
-
foreach ($relations as $key => $relation) {
- $subRelation = '';
+ $subRelation = [];
$closure = null;
- if ($relation instanceof \Closure) {
+ if ($relation instanceof Closure) {
$closure = $relation;
$relation = $key;
}
@@ -281,11 +313,13 @@ trait RelationShip
$subRelation = $relation;
$relation = $key;
} elseif (strpos($relation, '.')) {
- list($relation, $subRelation) = explode('.', $relation, 2);
+ [$relation, $subRelation] = explode('.', $relation, 2);
+
+ $subRelation = [$subRelation];
}
- $relation = Loader::parseName($relation, 1, false);
- $relationName = Loader::parseName($relation);
+ $relationName = $relation;
+ $relation = Str::camel($relation);
$relationResult = $this->$relation();
@@ -293,25 +327,58 @@ trait RelationShip
$relationResult->withAttr($withRelationAttr[$relationName]);
}
- $relationResult->eagerlyResult($result, $relation, $subRelation, $closure, $join);
+ if (is_scalar($cache)) {
+ $relationCache = [$cache];
+ } else {
+ $relationCache = $cache[$relationName] ?? [];
+ }
+
+ $relationResult->eagerlyResult($result, $relationName, $subRelation, $closure, $relationCache, $join);
}
}
+ /**
+ * 绑定(一对一)关联属性到当前模型
+ * @access protected
+ * @param string $relation 关联名称
+ * @param array $attrs 绑定属性
+ * @return $this
+ * @throws Exception
+ */
+ public function bindAttr(string $relation, array $attrs = [])
+ {
+ $relation = $this->getRelation($relation);
+
+ foreach ($attrs as $key => $attr) {
+ $key = is_numeric($key) ? $attr : $key;
+ $value = $this->getOrigin($key);
+
+ if (!is_null($value)) {
+ throw new Exception('bind attr has exists:' . $key);
+ }
+
+ $this->set($key, $relation ? $relation->$attr : null);
+ }
+
+ return $this;
+ }
+
/**
* 关联统计
* @access public
- * @param Model $result 数据对象
- * @param array $relations 关联名
- * @param string $aggregate 聚合查询方法
- * @param string $field 字段
+ * @param Query $query 查询对象
+ * @param array $relations 关联名
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param bool $useSubQuery 子查询
* @return void
*/
- public function relationCount(&$result, $relations, $aggregate = 'sum', $field = '*')
+ public function relationCount(Query $query, array $relations, string $aggregate = 'sum', string $field = '*', bool $useSubQuery = true): void
{
foreach ($relations as $key => $relation) {
$closure = $name = null;
- if ($relation instanceof \Closure) {
+ if ($relation instanceof Closure) {
$closure = $relation;
$relation = $key;
} elseif (is_string($key)) {
@@ -319,15 +386,23 @@ trait RelationShip
$relation = $key;
}
- $relation = Loader::parseName($relation, 1, false);
+ $relation = Str::camel($relation);
- $count = $this->$relation()->relationCount($result, $closure, $aggregate, $field, $name);
-
- if (empty($name)) {
- $name = Loader::parseName($relation) . '_' . $aggregate;
+ if ($useSubQuery) {
+ $count = $this->$relation()->getRelationCountQuery($closure, $aggregate, $field, $name);
+ } else {
+ $count = $this->$relation()->relationCount($this, $closure, $aggregate, $field, $name);
}
- $result->setAttr($name, $count);
+ if (empty($name)) {
+ $name = Str::snake($relation) . '_' . $aggregate;
+ }
+
+ if ($useSubQuery) {
+ $query->field(['(' . $count . ')' => $name]);
+ } else {
+ $this->setAttr($name, $count);
+ }
}
}
@@ -339,7 +414,7 @@ trait RelationShip
* @param string $localKey 当前主键
* @return HasOne
*/
- public function hasOne($model, $foreignKey = '', $localKey = '')
+ public function hasOne(string $model, string $foreignKey = '', string $localKey = ''): HasOne
{
// 记录当前关联信息
$model = $this->parseModel($model);
@@ -357,14 +432,14 @@ trait RelationShip
* @param string $localKey 关联主键
* @return BelongsTo
*/
- public function belongsTo($model, $foreignKey = '', $localKey = '')
+ public function belongsTo(string $model, string $foreignKey = '', string $localKey = ''): BelongsTo
{
// 记录当前关联信息
$model = $this->parseModel($model);
$foreignKey = $foreignKey ?: $this->getForeignKey((new $model)->getName());
$localKey = $localKey ?: (new $model)->getPk();
- $trace = debug_backtrace(false, 2);
- $relation = Loader::parseName($trace[1]['function']);
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
+ $relation = Str::snake($trace[1]['function']);
return new BelongsTo($this, $model, $foreignKey, $localKey, $relation);
}
@@ -377,7 +452,7 @@ trait RelationShip
* @param string $localKey 当前主键
* @return HasMany
*/
- public function hasMany($model, $foreignKey = '', $localKey = '')
+ public function hasMany(string $model, string $foreignKey = '', string $localKey = ''): HasMany
{
// 记录当前关联信息
$model = $this->parseModel($model);
@@ -395,9 +470,10 @@ trait RelationShip
* @param string $foreignKey 关联外键
* @param string $throughKey 关联外键
* @param string $localKey 当前主键
+ * @param string $throughPk 中间表主键
* @return HasManyThrough
*/
- public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '')
+ public function hasManyThrough(string $model, string $through, string $foreignKey = '', string $throughKey = '', string $localKey = '', string $throughPk = ''): HasManyThrough
{
// 记录当前关联信息
$model = $this->parseModel($model);
@@ -405,29 +481,54 @@ trait RelationShip
$localKey = $localKey ?: $this->getPk();
$foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
$throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName());
+ $throughPk = $throughPk ?: (new $through)->getPk();
- return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey);
+ return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $throughPk);
+ }
+
+ /**
+ * HAS ONE 远程关联定义
+ * @access public
+ * @param string $model 模型名
+ * @param string $through 中间模型名
+ * @param string $foreignKey 关联外键
+ * @param string $throughKey 关联外键
+ * @param string $localKey 当前主键
+ * @param string $throughPk 中间表主键
+ * @return HasOneThrough
+ */
+ public function hasOneThrough(string $model, string $through, string $foreignKey = '', string $throughKey = '', string $localKey = '', string $throughPk = ''): HasOneThrough
+ {
+ // 记录当前关联信息
+ $model = $this->parseModel($model);
+ $through = $this->parseModel($through);
+ $localKey = $localKey ?: $this->getPk();
+ $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
+ $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName());
+ $throughPk = $throughPk ?: (new $through)->getPk();
+
+ return new HasOneThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $throughPk);
}
/**
* BELONGS TO MANY 关联定义
* @access public
* @param string $model 模型名
- * @param string $table 中间表名
+ * @param string $middle 中间表/模型名
* @param string $foreignKey 关联外键
* @param string $localKey 当前模型关联键
* @return BelongsToMany
*/
- public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '')
+ public function belongsToMany(string $model, string $middle = '', string $foreignKey = '', string $localKey = ''): BelongsToMany
{
// 记录当前关联信息
$model = $this->parseModel($model);
- $name = Loader::parseName(basename(str_replace('\\', '/', $model)));
- $table = $table ?: Loader::parseName($this->name) . '_' . $name;
+ $name = Str::snake(class_basename($model));
+ $middle = $middle ?: Str::snake($this->name) . '_' . $name;
$foreignKey = $foreignKey ?: $name . '_id';
$localKey = $localKey ?: $this->getForeignKey($this->name);
- return new BelongsToMany($this, $model, $table, $foreignKey, $localKey);
+ return new BelongsToMany($this, $model, $middle, $foreignKey, $localKey);
}
/**
@@ -438,18 +539,18 @@ trait RelationShip
* @param string $type 多态类型
* @return MorphOne
*/
- public function morphOne($model, $morph = null, $type = '')
+ public function morphOne(string $model, $morph = null, string $type = ''): MorphOne
{
// 记录当前关联信息
$model = $this->parseModel($model);
if (is_null($morph)) {
- $trace = debug_backtrace(false, 2);
- $morph = Loader::parseName($trace[1]['function']);
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
+ $morph = Str::snake($trace[1]['function']);
}
if (is_array($morph)) {
- list($morphType, $foreignKey) = $morph;
+ [$morphType, $foreignKey] = $morph;
} else {
$morphType = $morph . '_type';
$foreignKey = $morph . '_id';
@@ -468,20 +569,20 @@ trait RelationShip
* @param string $type 多态类型
* @return MorphMany
*/
- public function morphMany($model, $morph = null, $type = '')
+ public function morphMany(string $model, $morph = null, string $type = ''): MorphMany
{
// 记录当前关联信息
$model = $this->parseModel($model);
if (is_null($morph)) {
- $trace = debug_backtrace(false, 2);
- $morph = Loader::parseName($trace[1]['function']);
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
+ $morph = Str::snake($trace[1]['function']);
}
$type = $type ?: get_class($this);
if (is_array($morph)) {
- list($morphType, $foreignKey) = $morph;
+ [$morphType, $foreignKey] = $morph;
} else {
$morphType = $morph . '_type';
$foreignKey = $morph . '_id';
@@ -497,10 +598,10 @@ trait RelationShip
* @param array $alias 多态别名定义
* @return MorphTo
*/
- public function morphTo($morph = null, $alias = [])
+ public function morphTo($morph = null, array $alias = []): MorphTo
{
- $trace = debug_backtrace(false, 2);
- $relation = Loader::parseName($trace[1]['function']);
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
+ $relation = Str::snake($trace[1]['function']);
if (is_null($morph)) {
$morph = $relation;
@@ -508,7 +609,7 @@ trait RelationShip
// 记录当前关联信息
if (is_array($morph)) {
- list($morphType, $foreignKey) = $morph;
+ [$morphType, $foreignKey] = $morph;
} else {
$morphType = $morph . '_type';
$foreignKey = $morph . '_id';
@@ -517,18 +618,77 @@ trait RelationShip
return new MorphTo($this, $morphType, $foreignKey, $alias, $relation);
}
+ /**
+ * MORPH TO MANY关联定义
+ * @access public
+ * @param string $model 模型名
+ * @param string $middle 中间表名/模型名
+ * @param string|array $morph 多态字段信息
+ * @param string $localKey 当前模型关联键
+ * @return MorphToMany
+ */
+ public function morphToMany(string $model, string $middle, $morph = null, string $localKey = null): MorphToMany
+ {
+ if (is_null($morph)) {
+ $morph = $middle;
+ }
+
+ // 记录当前关联信息
+ if (is_array($morph)) {
+ [$morphType, $morphKey] = $morph;
+ } else {
+ $morphType = $morph . '_type';
+ $morphKey = $morph . '_id';
+ }
+
+ $model = $this->parseModel($model);
+ $name = Str::snake(class_basename($model));
+ $localKey = $localKey ?: $this->getForeignKey($name);
+
+ return new MorphToMany($this, $model, $middle, $morphType, $morphKey, $localKey);
+ }
+
+ /**
+ * MORPH BY MANY关联定义
+ * @access public
+ * @param string $model 模型名
+ * @param string $middle 中间表名/模型名
+ * @param string|array $morph 多态字段信息
+ * @param string $foreignKey 关联外键
+ * @return MorphToMany
+ */
+ public function morphByMany(string $model, string $middle, $morph = null, string $foreignKey = null): MorphToMany
+ {
+ if (is_null($morph)) {
+ $morph = $middle;
+ }
+
+ // 记录当前关联信息
+ if (is_array($morph)) {
+ [$morphType, $morphKey] = $morph;
+ } else {
+ $morphType = $morph . '_type';
+ $morphKey = $morph . '_id';
+ }
+
+ $model = $this->parseModel($model);
+ $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
+
+ return new MorphToMany($this, $model, $middle, $morphType, $morphKey, $foreignKey, true);
+ }
+
/**
* 解析模型的完整命名空间
* @access protected
* @param string $model 模型名(或者完整类名)
* @return string
*/
- protected function parseModel($model)
+ protected function parseModel(string $model): string
{
if (false === strpos($model, '\\')) {
$path = explode('\\', static::class);
array_pop($path);
- array_push($path, Loader::parseName($model, 1));
+ array_push($path, Str::studly($model));
$model = implode('\\', $path);
}
@@ -541,13 +701,13 @@ trait RelationShip
* @param string $name 模型名
* @return string
*/
- protected function getForeignKey($name)
+ protected function getForeignKey(string $name): string
{
if (strpos($name, '\\')) {
- $name = basename(str_replace('\\', '/', $name));
+ $name = class_basename($name);
}
- return Loader::parseName($name) . '_id';
+ return Str::snake($name) . '_id';
}
/**
@@ -556,11 +716,11 @@ trait RelationShip
* @param string $attr 关联属性名
* @return string|false
*/
- protected function isRelationAttr($attr)
+ protected function isRelationAttr(string $attr)
{
- $relation = Loader::parseName($attr, 1, false);
+ $relation = Str::camel($attr);
- if (method_exists($this, $relation) && !method_exists('think\Model', $relation)) {
+ if ((method_exists($this, $relation) && !method_exists('think\Model', $relation)) || isset(static::$macro[static::class][$relation])) {
return $relation;
}
@@ -570,19 +730,18 @@ trait RelationShip
/**
* 智能获取关联模型数据
* @access protected
- * @param Relation $modelRelation 模型关联对象
+ * @param Relation $modelRelation 模型关联对象
* @return mixed
*/
protected function getRelationData(Relation $modelRelation)
{
- if ($this->parent && !$modelRelation->isSelfRelation() && get_class($this->parent) == get_class($modelRelation->getModel())) {
- $value = $this->parent;
- } else {
- // 获取关联数据
- $value = $modelRelation->getRelation();
+ if ($this->parent && !$modelRelation->isSelfRelation()
+ && get_class($this->parent) == get_class($modelRelation->getModel())) {
+ return $this->parent;
}
- return $value;
+ // 获取关联数据
+ return $modelRelation->getRelation();
}
/**
@@ -590,14 +749,14 @@ trait RelationShip
* @access protected
* @return void
*/
- protected function checkAutoRelationWrite()
+ protected function checkAutoRelationWrite(): void
{
foreach ($this->together as $key => $name) {
if (is_array($name)) {
if (key($name) === 0) {
$this->relationWrite[$key] = [];
// 绑定关联属性
- foreach ((array) $name as $val) {
+ foreach ($name as $val) {
if (isset($this->data[$val])) {
$this->relationWrite[$key][$val] = $this->data[$val];
}
@@ -620,15 +779,16 @@ trait RelationShip
* @access protected
* @return void
*/
- protected function autoRelationUpdate()
+ protected function autoRelationUpdate(): void
{
foreach ($this->relationWrite as $name => $val) {
if ($val instanceof Model) {
- $val->isUpdate()->save();
+ $val->exists(true)->save();
} else {
- $model = $this->getRelation($name);
+ $model = $this->getRelation($name, true);
+
if ($model instanceof Model) {
- $model->isUpdate()->save($val);
+ $model->exists(true)->save($val);
}
}
}
@@ -639,10 +799,10 @@ trait RelationShip
* @access protected
* @return void
*/
- protected function autoRelationInsert()
+ protected function autoRelationInsert(): void
{
foreach ($this->relationWrite as $name => $val) {
- $method = Loader::parseName($name, 1, false);
+ $method = Str::camel($name);
$this->$method()->save($val);
}
}
@@ -650,21 +810,33 @@ trait RelationShip
/**
* 自动关联数据删除(支持一对一及一对多关联)
* @access protected
+ * @param bool $force 强制删除
* @return void
*/
- protected function autoRelationDelete()
+ protected function autoRelationDelete($force = false): void
{
foreach ($this->relationWrite as $key => $name) {
$name = is_numeric($key) ? $name : $key;
- $result = $this->getRelation($name);
+ $result = $this->getRelation($name, true);
if ($result instanceof Model) {
- $result->delete();
+ $result->force($force)->delete();
} elseif ($result instanceof Collection) {
foreach ($result as $model) {
- $model->delete();
+ $model->force($force)->delete();
}
}
}
}
+
+ /**
+ * 移除当前模型的关联属性
+ * @access public
+ * @return $this
+ */
+ public function removeRelation()
+ {
+ $this->relation = [];
+ return $this;
+ }
}
diff --git a/thinkphp/library/think/model/concern/SoftDelete.php b/vendor/topthink/think-orm/src/model/concern/SoftDelete.php
old mode 100755
new mode 100644
similarity index 58%
rename from thinkphp/library/think/model/concern/SoftDelete.php
rename to vendor/topthink/think-orm/src/model/concern/SoftDelete.php
index 7dc96e126..5a9a56dc2
--- a/thinkphp/library/think/model/concern/SoftDelete.php
+++ b/vendor/topthink/think-orm/src/model/concern/SoftDelete.php
@@ -1,15 +1,26 @@
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\model\concern;
-use think\db\Query;
+use think\db\BaseQuery as Query;
+use think\Model;
/**
* 数据软删除
+ * @mixin Model
*/
trait SoftDelete
{
-
/**
* 是否包含软删除数据
* @var bool
@@ -19,9 +30,9 @@ trait SoftDelete
/**
* 判断当前实例是否被软删除
* @access public
- * @return boolean
+ * @return bool
*/
- public function trashed()
+ public function trashed(): bool
{
$field = $this->getDeleteTimeField();
@@ -37,11 +48,11 @@ trait SoftDelete
* @access public
* @return Query
*/
- public static function withTrashed()
+ public static function withTrashed(): Query
{
$model = new static();
- return $model->withTrashedData(true)->db(false);
+ return $model->withTrashedData(true)->db();
}
/**
@@ -50,7 +61,7 @@ trait SoftDelete
* @param bool $withTrashed 是否包含软删除数据
* @return $this
*/
- protected function withTrashedData($withTrashed)
+ protected function withTrashedData(bool $withTrashed)
{
$this->withTrashed = $withTrashed;
return $this;
@@ -61,18 +72,18 @@ trait SoftDelete
* @access public
* @return Query
*/
- public static function onlyTrashed()
+ public static function onlyTrashed(): Query
{
$model = new static();
$field = $model->getDeleteTimeField(true);
if ($field) {
return $model
- ->db(false)
+ ->db()
->useSoftDelete($field, $model->getWithTrashedExp());
}
- return $model->db(false);
+ return $model->db();
}
/**
@@ -80,10 +91,9 @@ trait SoftDelete
* @access protected
* @return array
*/
- protected function getWithTrashedExp()
+ protected function getWithTrashedExp(): array
{
- return is_null($this->defaultSoftDelete) ?
- ['notnull', ''] : ['<>', $this->defaultSoftDelete];
+ return is_null($this->defaultSoftDelete) ? ['notnull', ''] : ['<>', $this->defaultSoftDelete];
}
/**
@@ -91,20 +101,20 @@ trait SoftDelete
* @access public
* @return bool
*/
- public function delete($force = false)
+ public function delete(): bool
{
- if (!$this->isExists() || false === $this->trigger('before_delete', $this)) {
+ if (!$this->isExists() || $this->isEmpty() || false === $this->trigger('BeforeDelete')) {
return false;
}
- $force = $force ?: $this->isForce();
$name = $this->getDeleteTimeField();
+ $force = $this->isForce();
if ($name && !$force) {
// 软删除
- $this->data($name, $this->autoWriteTimestamp($name));
+ $this->set($name, $this->autoWriteTimestamp($name));
- $result = $this->isUpdate()->withEvent(false)->save();
+ $result = $this->exists()->withEvent(false)->save();
$this->withEvent(true);
} else {
@@ -112,18 +122,20 @@ trait SoftDelete
$where = $this->getWhere();
// 删除当前模型数据
- $result = $this->db(false)
+ $result = $this->db()
->where($where)
->removeOption('soft_delete')
->delete();
+
+ $this->lazySave(false);
}
// 关联删除
if (!empty($this->relationWrite)) {
- $this->autoRelationDelete();
+ $this->autoRelationDelete($force);
}
- $this->trigger('after_delete', $this);
+ $this->trigger('AfterDelete');
$this->exists(false);
@@ -137,10 +149,10 @@ trait SoftDelete
* @param bool $force 是否强制删除
* @return bool
*/
- public static function destroy($data, $force = false)
+ public static function destroy($data, bool $force = false): bool
{
// 包含软删除数据
- $query = (new static())->db(false);
+ $query = (new static())->withTrashedData(true)->db(false);
if (is_array($data) && key($data) !== 0) {
$query->where($data);
@@ -154,10 +166,8 @@ trait SoftDelete
$resultSet = $query->select($data);
- if ($resultSet) {
- foreach ($resultSet as $data) {
- $data->force($force)->delete();
- }
+ foreach ($resultSet as $result) {
+ $result->force($force)->delete();
}
return true;
@@ -169,33 +179,30 @@ trait SoftDelete
* @param array $where 更新条件
* @return bool
*/
- public function restore($where = [])
+ public function restore($where = []): bool
{
$name = $this->getDeleteTimeField();
- if ($name) {
- if (false === $this->trigger('before_restore')) {
- return false;
- }
-
- if (empty($where)) {
- $pk = $this->getPk();
-
- $where[] = [$pk, '=', $this->getData($pk)];
- }
-
- // 恢复删除
- $this->db(false)
- ->where($where)
- ->useSoftDelete($name, $this->getWithTrashedExp())
- ->update([$name => $this->defaultSoftDelete]);
-
- $this->trigger('after_restore');
-
- return true;
+ if (!$name || false === $this->trigger('BeforeRestore')) {
+ return false;
}
- return false;
+ if (empty($where)) {
+ $pk = $this->getPk();
+ if (is_string($pk)) {
+ $where[] = [$pk, '=', $this->getData($pk)];
+ }
+ }
+
+ // 恢复删除
+ $this->db(false)
+ ->where($where)
+ ->useSoftDelete($name, $this->getWithTrashedExp())
+ ->update([$name => $this->defaultSoftDelete]);
+
+ $this->trigger('AfterRestore');
+
+ return true;
}
/**
@@ -204,7 +211,7 @@ trait SoftDelete
* @param bool $read 是否查询操作 写操作的时候会自动去掉表别名
* @return string|false
*/
- protected function getDeleteTimeField($read = false)
+ protected function getDeleteTimeField(bool $read = false)
{
$field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? $this->deleteTime : 'delete_time';
@@ -230,12 +237,13 @@ trait SoftDelete
* @param Query $query
* @return void
*/
- protected function withNoTrashed($query)
+ protected function withNoTrashed(Query $query): void
{
$field = $this->getDeleteTimeField(true);
if ($field) {
- $query->useSoftDelete($field, $this->defaultSoftDelete);
+ $condition = is_null($this->defaultSoftDelete) ? ['null', ''] : ['=', $this->defaultSoftDelete];
+ $query->useSoftDelete($field, $condition);
}
}
}
diff --git a/vendor/topthink/think-orm/src/model/concern/TimeStamp.php b/vendor/topthink/think-orm/src/model/concern/TimeStamp.php
new file mode 100644
index 000000000..e207961f3
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/concern/TimeStamp.php
@@ -0,0 +1,208 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model\concern;
+
+use DateTime;
+
+/**
+ * 自动时间戳
+ */
+trait TimeStamp
+{
+ /**
+ * 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型
+ * @var bool|string
+ */
+ protected $autoWriteTimestamp;
+
+ /**
+ * 创建时间字段 false表示关闭
+ * @var false|string
+ */
+ protected $createTime = 'create_time';
+
+ /**
+ * 更新时间字段 false表示关闭
+ * @var false|string
+ */
+ protected $updateTime = 'update_time';
+
+ /**
+ * 时间字段显示格式
+ * @var string
+ */
+ protected $dateFormat;
+
+ /**
+ * 是否需要自动写入时间字段
+ * @access public
+ * @param bool|string $auto
+ * @return $this
+ */
+ public function isAutoWriteTimestamp($auto)
+ {
+ $this->autoWriteTimestamp = $this->checkTimeFieldType($auto);
+
+ return $this;
+ }
+
+ /**
+ * 检测时间字段的实际类型
+ * @access public
+ * @param bool|string $type
+ * @return mixed
+ */
+ protected function checkTimeFieldType($type)
+ {
+ if (true === $type) {
+ if (isset($this->type[$this->createTime])) {
+ $type = $this->type[$this->createTime];
+ } elseif (isset($this->schema[$this->createTime]) && in_array($this->schema[$this->createTime], ['datetime', 'date', 'timestamp', 'int'])) {
+ $type = $this->schema[$this->createTime];
+ } else {
+ $type = $this->getFieldType($this->createTime);
+ }
+ }
+
+ return $type;
+ }
+
+ /**
+ * 获取自动写入时间字段
+ * @access public
+ * @return bool|string
+ */
+ public function getAutoWriteTimestamp()
+ {
+ return $this->autoWriteTimestamp;
+ }
+
+ /**
+ * 设置时间字段格式化
+ * @access public
+ * @param string|false $format
+ * @return $this
+ */
+ public function setDateFormat($format)
+ {
+ $this->dateFormat = $format;
+
+ return $this;
+ }
+
+ /**
+ * 获取自动写入时间字段
+ * @access public
+ * @return string|false
+ */
+ public function getDateFormat()
+ {
+ return $this->dateFormat;
+ }
+
+ /**
+ * 自动写入时间戳
+ * @access protected
+ * @return mixed
+ */
+ protected function autoWriteTimestamp()
+ {
+ // 检测时间字段类型
+ $type = $this->checkTimeFieldType($this->autoWriteTimestamp);
+
+ return is_string($type) ? $this->getTimeTypeValue($type) : time();
+ }
+
+ /**
+ * 获取指定类型的时间字段值
+ * @access protected
+ * @param string $type 时间字段类型
+ * @return mixed
+ */
+ protected function getTimeTypeValue(string $type)
+ {
+ $value = time();
+
+ switch ($type) {
+ case 'datetime':
+ case 'date':
+ case 'timestamp':
+ $value = $this->formatDateTime('Y-m-d H:i:s.u');
+ break;
+ default:
+ if (false !== strpos($type, '\\')) {
+ // 对象数据写入
+ $obj = new $type();
+ if (method_exists($obj, '__toString')) {
+ // 对象数据写入
+ $value = $obj->__toString();
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * 时间日期字段格式化处理
+ * @access protected
+ * @param mixed $format 日期格式
+ * @param mixed $time 时间日期表达式
+ * @param bool $timestamp 时间表达式是否为时间戳
+ * @return mixed
+ */
+ protected function formatDateTime($format, $time = 'now', bool $timestamp = false)
+ {
+ if (empty($time)) {
+ return;
+ }
+
+ if (false === $format) {
+ return $time;
+ } elseif (false !== strpos($format, '\\')) {
+ return new $format($time);
+ }
+
+ if ($time instanceof DateTime) {
+ $dateTime = $time;
+ } elseif ($timestamp) {
+ $dateTime = new DateTime();
+ $dateTime->setTimestamp((int) $time);
+ } else {
+ $dateTime = new DateTime($time);
+ }
+
+ return $dateTime->format($format);
+ }
+
+ /**
+ * 获取时间字段值
+ * @access protected
+ * @param mixed $value
+ * @return mixed
+ */
+ protected function getTimestampValue($value)
+ {
+ $type = $this->checkTimeFieldType($this->autoWriteTimestamp);
+
+ if (is_string($type) && in_array(strtolower($type), [
+ 'datetime', 'date', 'timestamp',
+ ])) {
+ $value = $this->formatDateTime($this->dateFormat, $value);
+ } else {
+ $value = $this->formatDateTime($this->dateFormat, $value, true);
+ }
+
+ return $value;
+ }
+}
diff --git a/thinkphp/library/think/model/relation/BelongsTo.php b/vendor/topthink/think-orm/src/model/relation/BelongsTo.php
old mode 100755
new mode 100644
similarity index 54%
rename from thinkphp/library/think/model/relation/BelongsTo.php
rename to vendor/topthink/think-orm/src/model/relation/BelongsTo.php
index 98d176e8f..789c9440e
--- a/thinkphp/library/think/model/relation/BelongsTo.php
+++ b/vendor/topthink/think-orm/src/model/relation/BelongsTo.php
@@ -2,18 +2,24 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\model\relation;
-use think\Loader;
+use Closure;
+use think\db\BaseQuery as Query;
+use think\helper\Str;
use think\Model;
+/**
+ * BelongsTo关联类
+ */
class BelongsTo extends OneToOne
{
/**
@@ -25,13 +31,12 @@ class BelongsTo extends OneToOne
* @param string $localKey 关联主键
* @param string $relation 关联名
*/
- public function __construct(Model $parent, $model, $foreignKey, $localKey, $relation = null)
+ public function __construct(Model $parent, string $model, string $foreignKey, string $localKey, string $relation = null)
{
$this->parent = $parent;
$this->model = $model;
$this->foreignKey = $foreignKey;
$this->localKey = $localKey;
- $this->joinType = 'INNER';
$this->query = (new $model)->db();
$this->relation = $relation;
@@ -43,14 +48,14 @@ class BelongsTo extends OneToOne
/**
* 延迟获取关联数据
* @access public
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包查询条件
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
* @return Model
*/
- public function getRelation($subRelation = '', $closure = null)
+ public function getRelation(array $subRelation = [], Closure $closure = null)
{
if ($closure) {
- $closure($this->query);
+ $closure($this->getClosureType($closure));
}
$foreignKey = $this->foreignKey;
@@ -62,6 +67,11 @@ class BelongsTo extends OneToOne
->find();
if ($relationModel) {
+ if (!empty($this->bindAttr)) {
+ // 绑定关联属性
+ $this->bindAttr($this->parent, $relationModel);
+ }
+
$relationModel->setParent(clone $this->parent);
}
@@ -71,20 +81,16 @@ class BelongsTo extends OneToOne
/**
* 创建关联统计子查询
* @access public
- * @param \Closure $closure 闭包
- * @param string $aggregate 聚合查询方法
- * @param string $field 字段
- * @param string $aggregateAlias 聚合字段别名
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 聚合字段别名
* @return string
*/
- public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '')
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', &$name = ''): string
{
if ($closure) {
- $return = $closure($this->query);
-
- if ($return && is_string($return)) {
- $aggregateAlias = $return;
- }
+ $closure($this->getClosureType($closure), $name);
}
return $this->query
@@ -96,14 +102,14 @@ class BelongsTo extends OneToOne
/**
* 关联统计
* @access public
- * @param Model $result 数据对象
- * @param \Closure $closure 闭包
- * @param string $aggregate 聚合查询方法
- * @param string $field 字段
- * @param string $name 统计字段别名
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
* @return integer
*/
- public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '')
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null)
{
$foreignKey = $this->foreignKey;
@@ -112,11 +118,7 @@ class BelongsTo extends OneToOne
}
if ($closure) {
- $return = $closure($this->query);
-
- if ($return && is_string($return)) {
- $name = $return;
- }
+ $closure($this->getClosureType($closure), $name);
}
return $this->query
@@ -131,61 +133,76 @@ class BelongsTo extends OneToOne
* @param integer $count 个数
* @param string $id 关联表的统计字段
* @param string $joinType JOIN类型
+ * @param Query $query Query对象
* @return Query
*/
- public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query
{
$table = $this->query->getTable();
- $model = basename(str_replace('\\', '/', get_class($this->parent)));
- $relation = basename(str_replace('\\', '/', $this->model));
+ $model = class_basename($this->parent);
+ $relation = class_basename($this->model);
$localKey = $this->localKey;
$foreignKey = $this->foreignKey;
+ $softDelete = $this->query->getOptions('soft_delete');
+ $query = $query ?: $this->parent->db()->alias($model);
- return $this->parent->db()
- ->alias($model)
- ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) {
- $query->table([$table => $relation])
- ->field($relation . '.' . $localKey)
- ->whereExp($model . '.' . $foreignKey, '=' . $relation . '.' . $localKey);
- });
+ return $query->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey, $softDelete) {
+ $query->table([$table => $relation])
+ ->field($relation . '.' . $localKey)
+ ->whereExp($model . '.' . $foreignKey, '=' . $relation . '.' . $localKey)
+ ->when($softDelete, function ($query) use ($softDelete, $relation) {
+ $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ });
+ });
}
/**
* 根据关联条件查询当前模型
* @access public
- * @param mixed $where 查询条件(数组或者闭包)
- * @param mixed $fields 字段
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
* @return Query
*/
- public function hasWhere($where = [], $fields = null)
+ public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query
{
$table = $this->query->getTable();
- $model = basename(str_replace('\\', '/', get_class($this->parent)));
- $relation = basename(str_replace('\\', '/', $this->model));
+ $model = class_basename($this->parent);
+ $relation = class_basename($this->model);
if (is_array($where)) {
$this->getQueryWhere($where, $relation);
+ } elseif ($where instanceof Query) {
+ $where->via($relation);
+ } elseif ($where instanceof Closure) {
+ $where($this->query->via($relation));
+ $where = $this->query;
}
- $fields = $this->getRelationQueryFields($fields, $model);
+ $fields = $this->getRelationQueryFields($fields, $model);
+ $softDelete = $this->query->getOptions('soft_delete');
+ $query = $query ?: $this->parent->db()->alias($model);
- return $this->parent->db()
- ->alias($model)
- ->field($fields)
- ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType)
+ return $query->field($fields)
+ ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $joinType ?: $this->joinType)
+ ->when($softDelete, function ($query) use ($softDelete, $relation) {
+ $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ })
->where($where);
}
/**
* 预载入关联查询(数据集)
* @access protected
- * @param array $resultSet 数据集
- * @param string $relation 当前关联名
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
* @return void
*/
- protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure)
+ protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
{
$localKey = $this->localKey;
$foreignKey = $this->foreignKey;
@@ -203,10 +220,7 @@ class BelongsTo extends OneToOne
$data = $this->eagerlyWhere([
[$localKey, 'in', $range],
- ], $localKey, $relation, $subRelation, $closure);
-
- // 关联属性名
- $attr = Loader::parseName($relation);
+ ], $localKey, $subRelation, $closure, $cache);
// 关联数据封装
foreach ($resultSet as $result) {
@@ -216,15 +230,15 @@ class BelongsTo extends OneToOne
} else {
$relationModel = $data[$result->$foreignKey];
$relationModel->setParent(clone $result);
- $relationModel->isUpdate(true);
+ $relationModel->exists(true);
}
if (!empty($this->bindAttr)) {
// 绑定关联属性
- $this->bindAttr($relationModel, $result);
+ $this->bindAttr($result, $relationModel);
} else {
// 设置关联属性
- $result->setRelation($attr, $relationModel);
+ $result->setRelation($relation, $relationModel);
}
}
}
@@ -233,13 +247,14 @@ class BelongsTo extends OneToOne
/**
* 预载入关联查询(数据)
* @access protected
- * @param Model $result 数据对象
- * @param string $relation 当前关联名
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
* @return void
*/
- protected function eagerlyOne(&$result, $relation, $subRelation, $closure)
+ protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
{
$localKey = $this->localKey;
$foreignKey = $this->foreignKey;
@@ -248,7 +263,7 @@ class BelongsTo extends OneToOne
$data = $this->eagerlyWhere([
[$localKey, '=', $result->$foreignKey],
- ], $localKey, $relation, $subRelation, $closure);
+ ], $localKey, $subRelation, $closure, $cache);
// 关联模型
if (!isset($data[$result->$foreignKey])) {
@@ -256,25 +271,25 @@ class BelongsTo extends OneToOne
} else {
$relationModel = $data[$result->$foreignKey];
$relationModel->setParent(clone $result);
- $relationModel->isUpdate(true);
+ $relationModel->exists(true);
}
if (!empty($this->bindAttr)) {
// 绑定关联属性
- $this->bindAttr($relationModel, $result);
+ $this->bindAttr($result, $relationModel);
} else {
// 设置关联属性
- $result->setRelation(Loader::parseName($relation), $relationModel);
+ $result->setRelation($relation, $relationModel);
}
}
/**
* 添加关联数据
* @access public
- * @param Model $model 关联模型对象
+ * @param Model $model关联模型对象
* @return Model
*/
- public function associate($model)
+ public function associate(Model $model): Model
{
$this->parent->setAttr($this->foreignKey, $model->getKey());
$this->parent->save();
@@ -287,9 +302,11 @@ class BelongsTo extends OneToOne
* @access public
* @return Model
*/
- public function dissociate()
+ public function dissociate(): Model
{
- $this->parent->setAttr($this->foreignKey, null);
+ $foreignKey = $this->foreignKey;
+
+ $this->parent->setAttr($foreignKey, null);
$this->parent->save();
return $this->parent->setRelation($this->relation, null);
@@ -300,7 +317,7 @@ class BelongsTo extends OneToOne
* @access protected
* @return void
*/
- protected function baseQuery()
+ protected function baseQuery(): void
{
if (empty($this->baseQuery)) {
if (isset($this->parent->{$this->foreignKey})) {
diff --git a/thinkphp/library/think/model/relation/BelongsToMany.php b/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php
old mode 100755
new mode 100644
similarity index 59%
rename from thinkphp/library/think/model/relation/BelongsToMany.php
rename to vendor/topthink/think-orm/src/model/relation/BelongsToMany.php
index 747482b6e..6b64d9530
--- a/thinkphp/library/think/model/relation/BelongsToMany.php
+++ b/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php
@@ -2,7 +2,7 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
@@ -11,46 +11,66 @@
namespace think\model\relation;
+use Closure;
use think\Collection;
-use think\db\Query;
-use think\Exception;
-use think\Loader;
+use think\db\BaseQuery as Query;
+use think\db\exception\DbException as Exception;
+use think\db\Raw;
use think\Model;
use think\model\Pivot;
use think\model\Relation;
+use think\Paginator;
+/**
+ * 多对多关联类
+ */
class BelongsToMany extends Relation
{
- // 中间表表名
+ /**
+ * 中间表表名
+ * @var string
+ */
protected $middle;
- // 中间表模型名称
+
+ /**
+ * 中间表模型名称
+ * @var string
+ */
protected $pivotName;
- // 中间表数据名称
- protected $pivotDataName = 'pivot';
- // 中间表模型对象
+
+ /**
+ * 中间表模型对象
+ * @var Pivot
+ */
protected $pivot;
+ /**
+ * 中间表数据名称
+ * @var string
+ */
+ protected $pivotDataName = 'pivot';
+
/**
* 架构函数
* @access public
* @param Model $parent 上级模型对象
* @param string $model 模型名
- * @param string $table 中间表名
+ * @param string $middle 中间表/模型名
* @param string $foreignKey 关联模型外键
* @param string $localKey 当前模型关联键
*/
- public function __construct(Model $parent, $model, $table, $foreignKey, $localKey)
+ public function __construct(Model $parent, string $model, string $middle, string $foreignKey, string $localKey)
{
$this->parent = $parent;
$this->model = $model;
$this->foreignKey = $foreignKey;
$this->localKey = $localKey;
- if (false !== strpos($table, '\\')) {
- $this->pivotName = $table;
- $this->middle = basename(str_replace('\\', '/', $table));
+ if (false !== strpos($middle, '\\')) {
+ $this->pivotName = $middle;
+ $this->middle = class_basename($middle);
} else {
- $this->middle = $table;
+ $this->middle = $middle;
}
$this->query = (new $model)->db();
@@ -63,7 +83,7 @@ class BelongsToMany extends Relation
* @param $pivot
* @return $this
*/
- public function pivot($pivot)
+ public function pivot(string $pivot)
{
$this->pivotName = $pivot;
return $this;
@@ -75,43 +95,29 @@ class BelongsToMany extends Relation
* @param string $name
* @return $this
*/
- public function pivotDataName($name)
+ public function name(string $name)
{
$this->pivotDataName = $name;
return $this;
}
- /**
- * 获取中间表更新条件
- * @param $data
- * @return array
- */
- protected function getUpdateWhere($data)
- {
- return [
- $this->localKey => $data[$this->localKey],
- $this->foreignKey => $data[$this->foreignKey],
- ];
- }
-
/**
* 实例化中间表模型
* @access public
- * @param array $data
- * @param bool $isUpdate
+ * @param $data
* @return Pivot
* @throws Exception
*/
- protected function newPivot($data = [], $isUpdate = false)
+ protected function newPivot(array $data = []): Pivot
{
- $class = $this->pivotName ?: '\\think\\model\\Pivot';
+ $class = $this->pivotName ?: Pivot::class;
$pivot = new $class($data, $this->parent, $this->middle);
if ($pivot instanceof Pivot) {
- return $isUpdate ? $pivot->isUpdate(true, $this->getUpdateWhere($data)) : $pivot;
+ return $pivot;
+ } else {
+ throw new Exception('pivot model must extends: \think\model\Pivot');
}
-
- throw new Exception('pivot model must extends: \think\model\Pivot');
}
/**
@@ -119,14 +125,14 @@ class BelongsToMany extends Relation
* @access protected
* @param array|Collection|Paginator $models
*/
- protected function hydratePivot($models)
+ protected function hydratePivot(iterable $models)
{
foreach ($models as $model) {
$pivot = [];
foreach ($model->getData() as $key => $val) {
if (strpos($key, '__')) {
- list($name, $attr) = explode('__', $key, 2);
+ [$name, $attr] = explode('__', $key, 2);
if ('pivot' == $name) {
$pivot[$attr] = $val;
@@ -135,42 +141,27 @@ class BelongsToMany extends Relation
}
}
- $model->setRelation($this->pivotDataName, $this->newPivot($pivot, true));
+ $model->setRelation($this->pivotDataName, $this->newPivot($pivot));
}
}
- /**
- * 创建关联查询Query对象
- * @access protected
- * @return Query
- */
- protected function buildQuery()
- {
- $foreignKey = $this->foreignKey;
- $localKey = $this->localKey;
-
- // 关联查询
- $pk = $this->parent->getPk();
-
- $condition[] = ['pivot.' . $localKey, '=', $this->parent->$pk];
-
- return $this->belongsToManyQuery($foreignKey, $localKey, $condition);
- }
-
/**
* 延迟获取关联数据
* @access public
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包查询条件
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
* @return Collection
*/
- public function getRelation($subRelation = '', $closure = null)
+ public function getRelation(array $subRelation = [], Closure $closure = null): Collection
{
if ($closure) {
- $closure($this->query);
+ $closure($this->getClosureType($closure));
}
- $result = $this->buildQuery()->relation($subRelation)->select();
+ $result = $this->relation($subRelation)
+ ->select()
+ ->setParent(clone $this->parent);
+
$this->hydratePivot($result);
return $result;
@@ -182,9 +173,10 @@ class BelongsToMany extends Relation
* @param mixed $data
* @return Collection
*/
- public function select($data = null)
+ public function select($data = null): Collection
{
- $result = $this->buildQuery()->select($data);
+ $this->baseQuery();
+ $result = $this->query->select($data);
$this->hydratePivot($result);
return $result;
@@ -193,14 +185,14 @@ class BelongsToMany extends Relation
/**
* 重载paginate方法
* @access public
- * @param null $listRows
- * @param bool $simple
- * @param array $config
+ * @param int|array $listRows
+ * @param int|bool $simple
* @return Paginator
*/
- public function paginate($listRows = null, $simple = false, $config = [])
+ public function paginate($listRows = null, $simple = false): Paginator
{
- $result = $this->buildQuery()->paginate($listRows, $simple, $config);
+ $this->baseQuery();
+ $result = $this->query->paginate($listRows, $simple);
$this->hydratePivot($result);
return $result;
@@ -214,36 +206,16 @@ class BelongsToMany extends Relation
*/
public function find($data = null)
{
- $result = $this->buildQuery()->find($data);
- if ($result) {
+ $this->baseQuery();
+ $result = $this->query->find($data);
+
+ if ($result && !$result->isEmpty()) {
$this->hydratePivot([$result]);
}
return $result;
}
- /**
- * 查找多条记录 如果不存在则抛出异常
- * @access public
- * @param array|string|Query|\Closure $data
- * @return Collection
- */
- public function selectOrFail($data = null)
- {
- return $this->failException(true)->select($data);
- }
-
- /**
- * 查找单条记录 如果不存在则抛出异常
- * @access public
- * @param array|string|Query|\Closure $data
- * @return Model
- */
- public function findOrFail($data = null)
- {
- return $this->failException(true)->find($data);
- }
-
/**
* 根据关联条件查询当前模型
* @access public
@@ -251,9 +223,10 @@ class BelongsToMany extends Relation
* @param integer $count 个数
* @param string $id 关联表的统计字段
* @param string $joinType JOIN类型
- * @return Query
+ * @param Query $query Query对象
+ * @return Model
*/
- public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
+ public function has(string $operator = '>=', $count = 1, $id = '*', string $joinType = 'INNER', Query $query = null)
{
return $this->parent;
}
@@ -261,12 +234,14 @@ class BelongsToMany extends Relation
/**
* 根据关联条件查询当前模型
* @access public
- * @param mixed $where 查询条件(数组或者闭包)
- * @param mixed $fields 字段
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
* @return Query
* @throws Exception
*/
- public function hasWhere($where = [], $fields = null)
+ public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null)
{
throw new Exception('relation not support: hasWhere');
}
@@ -274,9 +249,9 @@ class BelongsToMany extends Relation
/**
* 设置中间表的查询条件
* @access public
- * @param string $field
- * @param string $op
- * @param mixed $condition
+ * @param string $field
+ * @param string $op
+ * @param mixed $condition
* @return $this
*/
public function wherePivot($field, $op = null, $condition = null)
@@ -288,19 +263,19 @@ class BelongsToMany extends Relation
/**
* 预载入关联查询(数据集)
* @access public
- * @param array $resultSet 数据集
- * @param string $relation 当前关联名
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
* @return void
*/
- public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure)
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
{
- $localKey = $this->localKey;
- $foreignKey = $this->foreignKey;
+ $localKey = $this->localKey;
+ $pk = $resultSet[0]->getPk();
+ $range = [];
- $pk = $resultSet[0]->getPk();
- $range = [];
foreach ($resultSet as $result) {
// 获取关联外键列表
if (isset($result->$pk)) {
@@ -312,10 +287,7 @@ class BelongsToMany extends Relation
// 查询关联数据
$data = $this->eagerlyManyToMany([
['pivot.' . $localKey, 'in', $range],
- ], $relation, $subRelation, $closure);
-
- // 关联属性名
- $attr = Loader::parseName($relation);
+ ], $subRelation, $closure, $cache);
// 关联数据封装
foreach ($resultSet as $result) {
@@ -323,7 +295,7 @@ class BelongsToMany extends Relation
$data[$result->$pk] = [];
}
- $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk]));
+ $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent));
}
}
}
@@ -331,13 +303,14 @@ class BelongsToMany extends Relation
/**
* 预载入关联查询(单个数据)
* @access public
- * @param Model $result 数据对象
- * @param string $relation 当前关联名
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
* @return void
*/
- public function eagerlyResult(&$result, $relation, $subRelation, $closure)
+ public function eagerlyResult(Model $result, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
{
$pk = $result->getPk();
@@ -346,28 +319,28 @@ class BelongsToMany extends Relation
// 查询管理数据
$data = $this->eagerlyManyToMany([
['pivot.' . $this->localKey, '=', $pk],
- ], $relation, $subRelation, $closure);
+ ], $subRelation, $closure, $cache);
// 关联数据封装
if (!isset($data[$pk])) {
$data[$pk] = [];
}
- $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk]));
+ $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent));
}
}
/**
* 关联统计
* @access public
- * @param Model $result 数据对象
- * @param \Closure $closure 闭包
- * @param string $aggregate 聚合查询方法
- * @param string $field 字段
- * @param string $name 统计字段别名
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
* @return integer
*/
- public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '')
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): float
{
$pk = $result->getPk();
@@ -378,11 +351,7 @@ class BelongsToMany extends Relation
$pk = $result->$pk;
if ($closure) {
- $return = $closure($this->query);
-
- if ($return && is_string($return)) {
- $name = $return;
- }
+ $closure($this->getClosureType($closure), $name);
}
return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
@@ -393,25 +362,21 @@ class BelongsToMany extends Relation
/**
* 获取关联统计子查询
* @access public
- * @param \Closure $closure 闭包
- * @param string $aggregate 聚合查询方法
- * @param string $field 字段
- * @param string $aggregateAlias 聚合字段别名
- * @return array
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return string
*/
- public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '')
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
{
if ($closure) {
- $return = $closure($this->query);
-
- if ($return && is_string($return)) {
- $aggregateAlias = $return;
- }
+ $closure($this->getClosureType($closure), $name);
}
return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
[
- 'pivot.' . $this->localKey, 'exp', $this->query->raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()),
+ 'pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->db(false)->getTable() . '.' . $this->parent->getPk()),
],
])->fetchSql()->$aggregate($field);
}
@@ -419,21 +384,22 @@ class BelongsToMany extends Relation
/**
* 多对多 关联模型预查询
* @access protected
- * @param array $where 关联预查询条件
- * @param string $relation 关联名
- * @param string $subRelation 子关联
- * @param \Closure $closure 闭包
+ * @param array $where 关联预查询条件
+ * @param array $subRelation 子关联
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
* @return array
*/
- protected function eagerlyManyToMany($where, $relation, $subRelation = '', $closure = null)
+ protected function eagerlyManyToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array
{
- // 预载入关联查询 支持嵌套预载入
if ($closure) {
- $closure($this->query);
+ $closure($this->getClosureType($closure));
}
+ // 预载入关联查询 支持嵌套预载入
$list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)
->with($subRelation)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
->select();
// 组装模型数据
@@ -442,17 +408,22 @@ class BelongsToMany extends Relation
$pivot = [];
foreach ($set->getData() as $key => $val) {
if (strpos($key, '__')) {
- list($name, $attr) = explode('__', $key, 2);
+ [$name, $attr] = explode('__', $key, 2);
if ('pivot' == $name) {
$pivot[$attr] = $val;
unset($set->$key);
}
}
}
+ $key = $pivot[$this->localKey];
- $set->setRelation($this->pivotDataName, $this->newPivot($pivot, true));
+ if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) {
+ continue;
+ }
- $data[$pivot[$this->localKey]][] = $set;
+ $set->setRelation($this->pivotDataName, $this->newPivot($pivot));
+
+ $data[$key][] = $set;
}
return $data;
@@ -461,29 +432,32 @@ class BelongsToMany extends Relation
/**
* BELONGS TO MANY 关联查询
* @access protected
- * @param string $foreignKey 关联模型关联键
- * @param string $localKey 当前模型关联键
- * @param array $condition 关联查询条件
+ * @param string $foreignKey 关联模型关联键
+ * @param string $localKey 当前模型关联键
+ * @param array $condition 关联查询条件
* @return Query
*/
- protected function belongsToManyQuery($foreignKey, $localKey, $condition = [])
+ protected function belongsToManyQuery(string $foreignKey, string $localKey, array $condition = []): Query
{
// 关联查询封装
- $tableName = $this->query->getTable();
- $table = $this->pivot->getTable();
- $fields = $this->getQueryFields($tableName);
-
- $query = $this->query
- ->field($fields)
- ->field(true, false, $table, 'pivot', 'pivot__');
-
if (empty($this->baseQuery)) {
- $relationFk = $this->query->getPk();
- $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk)
+ $tableName = $this->query->getTable();
+ $table = $this->pivot->db()->getTable();
+ $fields = $this->getQueryFields($tableName);
+
+ if ($this->withLimit) {
+ $this->query->limit($this->withLimit);
+ }
+
+ $this->query
+ ->field($fields)
+ ->tableField(true, $table, 'pivot', 'pivot__')
+ ->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $this->query->getPk())
->where($condition);
+
}
- return $query;
+ return $this->query;
}
/**
@@ -502,18 +476,18 @@ class BelongsToMany extends Relation
/**
* 批量保存当前关联数据对象
* @access public
- * @param array $dataSet 数据集
- * @param array $pivot 中间表额外数据
- * @param bool $samePivot 额外数据是否相同
+ * @param iterable $dataSet 数据集
+ * @param array $pivot 中间表额外数据
+ * @param bool $samePivot 额外数据是否相同
* @return array|false
*/
- public function saveAll(array $dataSet, array $pivot = [], $samePivot = false)
+ public function saveAll(iterable $dataSet, array $pivot = [], bool $samePivot = false)
{
$result = [];
foreach ($dataSet as $key => $data) {
if (!$samePivot) {
- $pivotData = isset($pivot[$key]) ? $pivot[$key] : [];
+ $pivotData = $pivot[$key] ?? [];
} else {
$pivotData = $pivot;
}
@@ -532,7 +506,7 @@ class BelongsToMany extends Relation
* @return array|Pivot
* @throws Exception
*/
- public function attach($data, $pivot = [])
+ public function attach($data, array $pivot = [])
{
if (is_array($data)) {
if (key($data) === 0) {
@@ -547,22 +521,21 @@ class BelongsToMany extends Relation
$id = $data;
} elseif ($data instanceof Model) {
// 根据关联表主键直接写入中间表
- $relationFk = $data->getPk();
- $id = $data->$relationFk;
+ $id = $data->getKey();
}
- if ($id) {
+ if (!empty($id)) {
// 保存中间表数据
- $pk = $this->parent->getPk();
- $pivot[$this->localKey] = $this->parent->$pk;
- $ids = (array) $id;
+ $pivot[$this->localKey] = $this->parent->getKey();
+ $ids = (array) $id;
foreach ($ids as $id) {
$pivot[$this->foreignKey] = $id;
$this->pivot->replace()
->exists(false)
+ ->data([])
->save($pivot);
- $result[] = $this->newPivot($pivot, true);
+ $result[] = $this->newPivot($pivot);
}
if (count($result) == 1) {
@@ -579,9 +552,8 @@ class BelongsToMany extends Relation
/**
* 判断是否存在关联数据
* @access public
- * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键
- * @return Pivot
- * @throws Exception
+ * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键
+ * @return Pivot|false
*/
public function attached($data)
{
@@ -606,7 +578,7 @@ class BelongsToMany extends Relation
* @param bool $relationDel 是否同时删除关联表数据
* @return integer
*/
- public function detach($data = null, $relationDel = false)
+ public function detach($data = null, bool $relationDel = false): int
{
if (is_array($data)) {
$id = $data;
@@ -615,13 +587,12 @@ class BelongsToMany extends Relation
$id = $data;
} elseif ($data instanceof Model) {
// 根据关联表主键直接写入中间表
- $relationFk = $data->getPk();
- $id = $data->$relationFk;
+ $id = $data->getKey();
}
// 删除中间表数据
- $pk = $this->parent->getPk();
- $pivot[] = [$this->localKey, '=', $this->parent->$pk];
+ $pivot = [];
+ $pivot[] = [$this->localKey, '=', $this->parent->getKey()];
if (isset($id)) {
$pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id];
@@ -645,7 +616,7 @@ class BelongsToMany extends Relation
* @param bool $detaching
* @return array
*/
- public function sync($ids, $detaching = true)
+ public function sync(array $ids, bool $detaching = true): array
{
$changes = [
'attached' => [],
@@ -653,10 +624,8 @@ class BelongsToMany extends Relation
'updated' => [],
];
- $pk = $this->parent->getPk();
-
$current = $this->pivot
- ->where($this->localKey, $this->parent->$pk)
+ ->where($this->localKey, $this->parent->getKey())
->column($this->foreignKey);
$records = [];
@@ -693,15 +662,21 @@ class BelongsToMany extends Relation
* @access protected
* @return void
*/
- protected function baseQuery()
+ protected function baseQuery(): void
{
- if (empty($this->baseQuery) && $this->parent->getData()) {
- $pk = $this->parent->getPk();
- $table = $this->pivot->getTable();
+ if (empty($this->baseQuery)) {
+ $foreignKey = $this->foreignKey;
+ $localKey = $this->localKey;
+
+ // 关联查询
+ if (null === $this->parent->getKey()) {
+ $condition = ['pivot.' . $localKey, 'exp', new Raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk())];
+ } else {
+ $condition = ['pivot.' . $localKey, '=', $this->parent->getKey()];
+ }
+
+ $this->belongsToManyQuery($foreignKey, $localKey, [$condition]);
- $this->query
- ->join([$table => 'pivot'], 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())
- ->where('pivot.' . $this->localKey, $this->parent->$pk);
$this->baseQuery = true;
}
}
diff --git a/vendor/topthink/think-orm/src/model/relation/HasMany.php b/vendor/topthink/think-orm/src/model/relation/HasMany.php
new file mode 100644
index 000000000..a67d41b03
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/relation/HasMany.php
@@ -0,0 +1,367 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model\relation;
+
+use Closure;
+use think\Collection;
+use think\db\BaseQuery as Query;
+use think\helper\Str;
+use think\Model;
+use think\model\Relation;
+
+/**
+ * 一对多关联类
+ */
+class HasMany extends Relation
+{
+ /**
+ * 架构函数
+ * @access public
+ * @param Model $parent 上级模型对象
+ * @param string $model 模型名
+ * @param string $foreignKey 关联外键
+ * @param string $localKey 当前模型主键
+ */
+ public function __construct(Model $parent, string $model, string $foreignKey, string $localKey)
+ {
+ $this->parent = $parent;
+ $this->model = $model;
+ $this->foreignKey = $foreignKey;
+ $this->localKey = $localKey;
+ $this->query = (new $model)->db();
+
+ if (get_class($parent) == $model) {
+ $this->selfRelation = true;
+ }
+ }
+
+ /**
+ * 延迟获取关联数据
+ * @access public
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
+ * @return Collection
+ */
+ public function getRelation(array $subRelation = [], Closure $closure = null): Collection
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ if ($this->withLimit) {
+ $this->query->limit($this->withLimit);
+ }
+
+ return $this->query
+ ->where($this->foreignKey, $this->parent->{$this->localKey})
+ ->relation($subRelation)
+ ->select()
+ ->setParent(clone $this->parent);
+ }
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+ $range = [];
+
+ foreach ($resultSet as $result) {
+ // 获取关联外键列表
+ if (isset($result->$localKey)) {
+ $range[] = $result->$localKey;
+ }
+ }
+
+ if (!empty($range)) {
+ $data = $this->eagerlyOneToMany([
+ [$this->foreignKey, 'in', $range],
+ ], $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ foreach ($resultSet as $result) {
+ $pk = $result->$localKey;
+ if (!isset($data[$pk])) {
+ $data[$pk] = [];
+ }
+
+ $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent));
+ }
+ }
+ }
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+
+ if (isset($result->$localKey)) {
+ $pk = $result->$localKey;
+ $data = $this->eagerlyOneToMany([
+ [$this->foreignKey, '=', $pk],
+ ], $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ if (!isset($data[$pk])) {
+ $data[$pk] = [];
+ }
+
+ $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent));
+ }
+ }
+
+ /**
+ * 关联统计
+ * @access public
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return integer
+ */
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null)
+ {
+ $localKey = $this->localKey;
+
+ if (!isset($result->$localKey)) {
+ return 0;
+ }
+
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->query
+ ->where($this->foreignKey, '=', $result->$localKey)
+ ->$aggregate($field);
+ }
+
+ /**
+ * 创建关联统计子查询
+ * @access public
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return string
+ */
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->query->alias($aggregate . '_table')
+ ->whereExp($aggregate . '_table.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey)
+ ->fetchSql()
+ ->$aggregate($field);
+ }
+
+ /**
+ * 一对多 关联模型预查询
+ * @access public
+ * @param array $where 关联预查询条件
+ * @param array $subRelation 子关联
+ * @param Closure $closure
+ * @param array $cache 关联缓存
+ * @return array
+ */
+ protected function eagerlyOneToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array
+ {
+ $foreignKey = $this->foreignKey;
+
+ $this->query->removeWhereField($this->foreignKey);
+
+ // 预载入关联查询 支持嵌套预载入
+ if ($closure) {
+ $this->baseQuery = true;
+ $closure($this->getClosureType($closure));
+ }
+
+ $list = $this->query
+ ->where($where)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->with($subRelation)
+ ->select();
+
+ // 组装模型数据
+ $data = [];
+
+ foreach ($list as $set) {
+ $key = $set->$foreignKey;
+
+ if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) {
+ continue;
+ }
+
+ $data[$key][] = $set;
+ }
+
+ return $data;
+ }
+
+ /**
+ * 保存(新增)当前关联数据对象
+ * @access public
+ * @param mixed $data 数据 可以使用数组 关联模型对象
+ * @param boolean $replace 是否自动识别更新和写入
+ * @return Model|false
+ */
+ public function save($data, bool $replace = true)
+ {
+ $model = $this->make();
+
+ return $model->replace($replace)->save($data) ? $model : false;
+ }
+
+ /**
+ * 创建关联对象实例
+ * @param array|Model $data
+ * @return Model
+ */
+ public function make($data = []): Model
+ {
+ if ($data instanceof Model) {
+ $data = $data->getData();
+ }
+
+ // 保存关联表数据
+ $data[$this->foreignKey] = $this->parent->{$this->localKey};
+
+ return new $this->model($data);
+ }
+
+ /**
+ * 批量保存当前关联数据对象
+ * @access public
+ * @param iterable $dataSet 数据集
+ * @param boolean $replace 是否自动识别更新和写入
+ * @return array|false
+ */
+ public function saveAll(iterable $dataSet, bool $replace = true)
+ {
+ $result = [];
+
+ foreach ($dataSet as $key => $data) {
+ $result[] = $this->save($data, $replace);
+ }
+
+ return empty($result) ? false : $result;
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER', Query $query = null): Query
+ {
+ $table = $this->query->getTable();
+
+ $model = class_basename($this->parent);
+ $relation = class_basename($this->model);
+
+ if ('*' != $id) {
+ $id = $relation . '.' . (new $this->model)->getPk();
+ }
+
+ $softDelete = $this->query->getOptions('soft_delete');
+ $query = $query ?: $this->parent->db()->alias($model);
+
+ return $query->field($model . '.*')
+ ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType)
+ ->when($softDelete, function ($query) use ($softDelete, $relation) {
+ $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ })
+ ->group($relation . '.' . $this->foreignKey)
+ ->having('count(' . $id . ')' . $operator . $count);
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query
+ {
+ $table = $this->query->getTable();
+ $model = class_basename($this->parent);
+ $relation = class_basename($this->model);
+
+ if (is_array($where)) {
+ $this->getQueryWhere($where, $relation);
+ } elseif ($where instanceof Query) {
+ $where->via($relation);
+ } elseif ($where instanceof Closure) {
+ $where($this->query->via($relation));
+ $where = $this->query;
+ }
+
+ $fields = $this->getRelationQueryFields($fields, $model);
+ $softDelete = $this->query->getOptions('soft_delete');
+ $query = $query ?: $this->parent->db()->alias($model);
+
+ return $query->group($model . '.' . $this->localKey)
+ ->field($fields)
+ ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType)
+ ->when($softDelete, function ($query) use ($softDelete, $relation) {
+ $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ })
+ ->where($where);
+ }
+
+ /**
+ * 执行基础查询(仅执行一次)
+ * @access protected
+ * @return void
+ */
+ protected function baseQuery(): void
+ {
+ if (empty($this->baseQuery)) {
+ if (isset($this->parent->{$this->localKey})) {
+ // 关联查询带入关联条件
+ $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey});
+ }
+
+ $this->baseQuery = true;
+ }
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php b/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php
new file mode 100644
index 000000000..30d5ca41c
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php
@@ -0,0 +1,382 @@
+
+// +----------------------------------------------------------------------
+
+namespace think\model\relation;
+
+use Closure;
+use think\Collection;
+use think\db\BaseQuery as Query;
+use think\helper\Str;
+use think\Model;
+use think\model\Relation;
+
+/**
+ * 远程一对多关联类
+ */
+class HasManyThrough extends Relation
+{
+ /**
+ * 中间关联表外键
+ * @var string
+ */
+ protected $throughKey;
+
+ /**
+ * 中间主键
+ * @var string
+ */
+ protected $throughPk;
+
+ /**
+ * 中间表查询对象
+ * @var Query
+ */
+ protected $through;
+
+ /**
+ * 架构函数
+ * @access public
+ * @param Model $parent 上级模型对象
+ * @param string $model 关联模型名
+ * @param string $through 中间模型名
+ * @param string $foreignKey 关联外键
+ * @param string $throughKey 中间关联外键
+ * @param string $localKey 当前模型主键
+ * @param string $throughPk 中间模型主键
+ */
+ public function __construct(Model $parent, string $model, string $through, string $foreignKey, string $throughKey, string $localKey, string $throughPk)
+ {
+ $this->parent = $parent;
+ $this->model = $model;
+ $this->through = (new $through)->db();
+ $this->foreignKey = $foreignKey;
+ $this->throughKey = $throughKey;
+ $this->localKey = $localKey;
+ $this->throughPk = $throughPk;
+ $this->query = (new $model)->db();
+ }
+
+ /**
+ * 延迟获取关联数据
+ * @access public
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
+ * @return Collection
+ */
+ public function getRelation(array $subRelation = [], Closure $closure = null)
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ $this->baseQuery();
+
+ if ($this->withLimit) {
+ $this->query->limit($this->withLimit);
+ }
+
+ return $this->query->relation($subRelation)
+ ->select()
+ ->setParent(clone $this->parent);
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query
+ {
+ $model = Str::snake(class_basename($this->parent));
+ $throughTable = $this->through->getTable();
+ $pk = $this->throughPk;
+ $throughKey = $this->throughKey;
+ $relation = new $this->model;
+ $relationTable = $relation->getTable();
+ $softDelete = $this->query->getOptions('soft_delete');
+
+ if ('*' != $id) {
+ $id = $relationTable . '.' . $relation->getPk();
+ }
+ $query = $query ?: $this->parent->db()->alias($model);
+
+ return $query->field($model . '.*')
+ ->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey)
+ ->join($relationTable, $relationTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk)
+ ->when($softDelete, function ($query) use ($softDelete, $relationTable) {
+ $query->where($relationTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ })
+ ->group($relationTable . '.' . $this->throughKey)
+ ->having('count(' . $id . ')' . $operator . $count);
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function hasWhere($where = [], $fields = null, $joinType = '', Query $query = null): Query
+ {
+ $model = Str::snake(class_basename($this->parent));
+ $throughTable = $this->through->getTable();
+ $pk = $this->throughPk;
+ $throughKey = $this->throughKey;
+ $modelTable = (new $this->model)->getTable();
+
+ if (is_array($where)) {
+ $this->getQueryWhere($where, $modelTable);
+ } elseif ($where instanceof Query) {
+ $where->via($modelTable);
+ } elseif ($where instanceof Closure) {
+ $where($this->query->via($modelTable));
+ $where = $this->query;
+ }
+
+ $fields = $this->getRelationQueryFields($fields, $model);
+ $softDelete = $this->query->getOptions('soft_delete');
+ $query = $query ?: $this->parent->db()->alias($model);
+
+ return $query->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey)
+ ->join($modelTable, $modelTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk, $joinType)
+ ->when($softDelete, function ($query) use ($softDelete, $modelTable) {
+ $query->where($modelTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ })
+ ->group($modelTable . '.' . $this->throughKey)
+ ->where($where)
+ ->field($fields);
+ }
+
+ /**
+ * 预载入关联查询(数据集)
+ * @access protected
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+ $foreignKey = $this->foreignKey;
+
+ $range = [];
+ foreach ($resultSet as $result) {
+ // 获取关联外键列表
+ if (isset($result->$localKey)) {
+ $range[] = $result->$localKey;
+ }
+ }
+
+ if (!empty($range)) {
+ $this->query->removeWhereField($foreignKey);
+
+ $data = $this->eagerlyWhere([
+ [$this->foreignKey, 'in', $range],
+ ], $foreignKey, $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ foreach ($resultSet as $result) {
+ $pk = $result->$localKey;
+ if (!isset($data[$pk])) {
+ $data[$pk] = [];
+ }
+
+ // 设置关联属性
+ $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent));
+ }
+ }
+ }
+
+ /**
+ * 预载入关联查询(数据)
+ * @access protected
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+ $foreignKey = $this->foreignKey;
+ $pk = $result->$localKey;
+
+ $this->query->removeWhereField($foreignKey);
+
+ $data = $this->eagerlyWhere([
+ [$foreignKey, '=', $pk],
+ ], $foreignKey, $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ if (!isset($data[$pk])) {
+ $data[$pk] = [];
+ }
+
+ $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent));
+ }
+
+ /**
+ * 关联模型预查询
+ * @access public
+ * @param array $where 关联预查询条件
+ * @param string $key 关联键名
+ * @param array $subRelation 子关联
+ * @param Closure $closure
+ * @param array $cache 关联缓存
+ * @return array
+ */
+ protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []): array
+ {
+ // 预载入关联查询 支持嵌套预载入
+ $throughList = $this->through->where($where)->select();
+ $keys = $throughList->column($this->throughPk, $this->throughPk);
+
+ if ($closure) {
+ $this->baseQuery = true;
+ $closure($this->getClosureType($closure));
+ }
+
+ $list = $this->query
+ ->where($this->throughKey, 'in', $keys)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->select();
+
+ // 组装模型数据
+ $data = [];
+ $keys = $throughList->column($this->foreignKey, $this->throughPk);
+
+ foreach ($list as $set) {
+ $key = $keys[$set->{$this->throughKey}];
+
+ if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) {
+ continue;
+ }
+
+ $data[$key][] = $set;
+ }
+
+ return $data;
+ }
+
+ /**
+ * 关联统计
+ * @access public
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return mixed
+ */
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null)
+ {
+ $localKey = $this->localKey;
+
+ if (!isset($result->$localKey)) {
+ return 0;
+ }
+
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ $alias = Str::snake(class_basename($this->model));
+ $throughTable = $this->through->getTable();
+ $pk = $this->throughPk;
+ $throughKey = $this->throughKey;
+ $modelTable = $this->parent->getTable();
+
+ if (false === strpos($field, '.')) {
+ $field = $alias . '.' . $field;
+ }
+
+ return $this->query
+ ->alias($alias)
+ ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
+ ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey)
+ ->where($throughTable . '.' . $this->foreignKey, $result->$localKey)
+ ->$aggregate($field);
+ }
+
+ /**
+ * 创建关联统计子查询
+ * @access public
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return string
+ */
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ $alias = Str::snake(class_basename($this->model));
+ $throughTable = $this->through->getTable();
+ $pk = $this->throughPk;
+ $throughKey = $this->throughKey;
+ $modelTable = $this->parent->getTable();
+
+ if (false === strpos($field, '.')) {
+ $field = $alias . '.' . $field;
+ }
+
+ return $this->query
+ ->alias($alias)
+ ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
+ ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey)
+ ->whereExp($throughTable . '.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey)
+ ->fetchSql()
+ ->$aggregate($field);
+ }
+
+ /**
+ * 执行基础查询(仅执行一次)
+ * @access protected
+ * @return void
+ */
+ protected function baseQuery(): void
+ {
+ if (empty($this->baseQuery) && $this->parent->getData()) {
+ $alias = Str::snake(class_basename($this->model));
+ $throughTable = $this->through->getTable();
+ $pk = $this->throughPk;
+ $throughKey = $this->throughKey;
+ $modelTable = $this->parent->getTable();
+ $fields = $this->getQueryFields($alias);
+
+ $this->query
+ ->field($fields)
+ ->alias($alias)
+ ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
+ ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey)
+ ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey});
+
+ $this->baseQuery = true;
+ }
+ }
+
+}
diff --git a/thinkphp/library/think/model/relation/HasOne.php b/vendor/topthink/think-orm/src/model/relation/HasOne.php
old mode 100755
new mode 100644
similarity index 52%
rename from thinkphp/library/think/model/relation/HasOne.php
rename to vendor/topthink/think-orm/src/model/relation/HasOne.php
index d8e3ec798..7fcd20a03
--- a/thinkphp/library/think/model/relation/HasOne.php
+++ b/vendor/topthink/think-orm/src/model/relation/HasOne.php
@@ -2,19 +2,24 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think\model\relation;
-use think\db\Query;
-use think\Loader;
+use Closure;
+use think\db\BaseQuery as Query;
+use think\helper\Str;
use think\Model;
+/**
+ * HasOne 关联类
+ */
class HasOne extends OneToOne
{
/**
@@ -25,13 +30,12 @@ class HasOne extends OneToOne
* @param string $foreignKey 关联外键
* @param string $localKey 当前模型主键
*/
- public function __construct(Model $parent, $model, $foreignKey, $localKey)
+ public function __construct(Model $parent, string $model, string $foreignKey, string $localKey)
{
$this->parent = $parent;
$this->model = $model;
$this->foreignKey = $foreignKey;
$this->localKey = $localKey;
- $this->joinType = 'INNER';
$this->query = (new $model)->db();
if (get_class($parent) == $model) {
@@ -42,16 +46,16 @@ class HasOne extends OneToOne
/**
* 延迟获取关联数据
* @access public
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包查询条件
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
* @return Model
*/
- public function getRelation($subRelation = '', $closure = null)
+ public function getRelation(array $subRelation = [], Closure $closure = null)
{
$localKey = $this->localKey;
if ($closure) {
- $closure($this->query);
+ $closure($this->getClosureType($closure));
}
// 判断关联类型执行查询
@@ -62,6 +66,11 @@ class HasOne extends OneToOne
->find();
if ($relationModel) {
+ if (!empty($this->bindAttr)) {
+ // 绑定关联属性
+ $this->bindAttr($this->parent, $relationModel);
+ }
+
$relationModel->setParent(clone $this->parent);
}
@@ -71,20 +80,16 @@ class HasOne extends OneToOne
/**
* 创建关联统计子查询
* @access public
- * @param \Closure $closure 闭包
- * @param string $aggregate 聚合查询方法
- * @param string $field 字段
- * @param string $aggregateAlias 聚合字段别名
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
* @return string
*/
- public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '')
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
{
if ($closure) {
- $return = $closure($this->query);
-
- if ($return && is_string($return)) {
- $aggregateAlias = $return;
- }
+ $closure($this->getClosureType($closure), $name);
}
return $this->query
@@ -96,14 +101,14 @@ class HasOne extends OneToOne
/**
* 关联统计
* @access public
- * @param Model $result 数据对象
- * @param \Closure $closure 闭包
- * @param string $aggregate 聚合查询方法
- * @param string $field 字段
- * @param string $name 统计字段别名
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
* @return integer
*/
- public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '')
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null)
{
$localKey = $this->localKey;
@@ -112,10 +117,7 @@ class HasOne extends OneToOne
}
if ($closure) {
- $return = $closure($this->query);
- if ($return && is_string($return)) {
- $name = $return;
- }
+ $closure($this->getClosureType($closure), $name);
}
return $this->query
@@ -130,61 +132,76 @@ class HasOne extends OneToOne
* @param integer $count 个数
* @param string $id 关联表的统计字段
* @param string $joinType JOIN类型
+ * @param Query $query Query对象
* @return Query
*/
- public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query
{
$table = $this->query->getTable();
- $model = basename(str_replace('\\', '/', get_class($this->parent)));
- $relation = basename(str_replace('\\', '/', $this->model));
+ $model = class_basename($this->parent);
+ $relation = class_basename($this->model);
$localKey = $this->localKey;
$foreignKey = $this->foreignKey;
+ $softDelete = $this->query->getOptions('soft_delete');
+ $query = $query ?: $this->parent->db()->alias($model);
- return $this->parent->db()
- ->alias($model)
- ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) {
- $query->table([$table => $relation])
- ->field($relation . '.' . $foreignKey)
- ->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey);
- });
+ return $query->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey, $softDelete) {
+ $query->table([$table => $relation])
+ ->field($relation . '.' . $foreignKey)
+ ->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey)
+ ->when($softDelete, function ($query) use ($softDelete, $relation) {
+ $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ });
+ });
}
/**
* 根据关联条件查询当前模型
* @access public
- * @param mixed $where 查询条件(数组或者闭包)
- * @param mixed $fields 字段
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
* @return Query
*/
- public function hasWhere($where = [], $fields = null)
+ public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query
{
$table = $this->query->getTable();
- $model = basename(str_replace('\\', '/', get_class($this->parent)));
- $relation = basename(str_replace('\\', '/', $this->model));
+ $model = class_basename($this->parent);
+ $relation = class_basename($this->model);
if (is_array($where)) {
$this->getQueryWhere($where, $relation);
+ } elseif ($where instanceof Query) {
+ $where->via($relation);
+ } elseif ($where instanceof Closure) {
+ $where($this->query->via($relation));
+ $where = $this->query;
}
- $fields = $this->getRelationQueryFields($fields, $model);
+ $fields = $this->getRelationQueryFields($fields, $model);
+ $softDelete = $this->query->getOptions('soft_delete');
+ $query = $query ? $query->alias($model) : $this->parent->db()->alias($model);
- return $this->parent->db()
- ->alias($model)
- ->field($fields)
- ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType)
+ return $query->field($fields)
+ ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType ?: $this->joinType)
+ ->when($softDelete, function ($query) use ($softDelete, $relation) {
+ $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ })
->where($where);
}
/**
* 预载入关联查询(数据集)
* @access protected
- * @param array $resultSet 数据集
- * @param string $relation 当前关联名
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
* @return void
*/
- protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure)
+ protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
{
$localKey = $this->localKey;
$foreignKey = $this->foreignKey;
@@ -202,10 +219,7 @@ class HasOne extends OneToOne
$data = $this->eagerlyWhere([
[$foreignKey, 'in', $range],
- ], $foreignKey, $relation, $subRelation, $closure);
-
- // 关联属性名
- $attr = Loader::parseName($relation);
+ ], $foreignKey, $subRelation, $closure, $cache);
// 关联数据封装
foreach ($resultSet as $result) {
@@ -215,15 +229,15 @@ class HasOne extends OneToOne
} else {
$relationModel = $data[$result->$localKey];
$relationModel->setParent(clone $result);
- $relationModel->isUpdate(true);
+ $relationModel->exists(true);
}
if (!empty($this->bindAttr)) {
// 绑定关联属性
- $this->bindAttr($relationModel, $result, $this->bindAttr);
+ $this->bindAttr($result, $relationModel);
} else {
// 设置关联属性
- $result->setRelation($attr, $relationModel);
+ $result->setRelation($relation, $relationModel);
}
}
}
@@ -232,13 +246,14 @@ class HasOne extends OneToOne
/**
* 预载入关联查询(数据)
* @access protected
- * @param Model $result 数据对象
- * @param string $relation 当前关联名
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
* @return void
*/
- protected function eagerlyOne(&$result, $relation, $subRelation, $closure)
+ protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
{
$localKey = $this->localKey;
$foreignKey = $this->foreignKey;
@@ -247,7 +262,7 @@ class HasOne extends OneToOne
$data = $this->eagerlyWhere([
[$foreignKey, '=', $result->$localKey],
- ], $foreignKey, $relation, $subRelation, $closure);
+ ], $foreignKey, $subRelation, $closure, $cache);
// 关联模型
if (!isset($data[$result->$localKey])) {
@@ -255,14 +270,14 @@ class HasOne extends OneToOne
} else {
$relationModel = $data[$result->$localKey];
$relationModel->setParent(clone $result);
- $relationModel->isUpdate(true);
+ $relationModel->exists(true);
}
if (!empty($this->bindAttr)) {
// 绑定关联属性
- $this->bindAttr($relationModel, $result, $this->bindAttr);
+ $this->bindAttr($result, $relationModel);
} else {
- $result->setRelation(Loader::parseName($relation), $relationModel);
+ $result->setRelation($relation, $relationModel);
}
}
@@ -271,7 +286,7 @@ class HasOne extends OneToOne
* @access protected
* @return void
*/
- protected function baseQuery()
+ protected function baseQuery(): void
{
if (empty($this->baseQuery)) {
if (isset($this->parent->{$this->localKey})) {
diff --git a/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php b/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php
new file mode 100644
index 000000000..8ec42df47
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php
@@ -0,0 +1,163 @@
+
+// +----------------------------------------------------------------------
+
+namespace think\model\relation;
+
+use Closure;
+use think\helper\Str;
+use think\Model;
+
+/**
+ * 远程一对一关联类
+ */
+class HasOneThrough extends HasManyThrough
+{
+
+ /**
+ * 延迟获取关联数据
+ * @access public
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
+ * @return Model
+ */
+ public function getRelation(array $subRelation = [], Closure $closure = null)
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ $this->baseQuery();
+
+ $relationModel = $this->query->relation($subRelation)->find();
+
+ if ($relationModel) {
+ $relationModel->setParent(clone $this->parent);
+ }
+
+ return $relationModel;
+ }
+
+ /**
+ * 预载入关联查询(数据集)
+ * @access protected
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+ $foreignKey = $this->foreignKey;
+
+ $range = [];
+ foreach ($resultSet as $result) {
+ // 获取关联外键列表
+ if (isset($result->$localKey)) {
+ $range[] = $result->$localKey;
+ }
+ }
+
+ if (!empty($range)) {
+ $this->query->removeWhereField($foreignKey);
+
+ $data = $this->eagerlyWhere([
+ [$this->foreignKey, 'in', $range],
+ ], $foreignKey, $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ foreach ($resultSet as $result) {
+ // 关联模型
+ if (!isset($data[$result->$localKey])) {
+ $relationModel = null;
+ } else {
+ $relationModel = $data[$result->$localKey];
+ $relationModel->setParent(clone $result);
+ $relationModel->exists(true);
+ }
+
+ // 设置关联属性
+ $result->setRelation($relation, $relationModel);
+ }
+ }
+ }
+
+ /**
+ * 预载入关联查询(数据)
+ * @access protected
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+ $foreignKey = $this->foreignKey;
+
+ $this->query->removeWhereField($foreignKey);
+
+ $data = $this->eagerlyWhere([
+ [$foreignKey, '=', $result->$localKey],
+ ], $foreignKey, $subRelation, $closure, $cache);
+
+ // 关联模型
+ if (!isset($data[$result->$localKey])) {
+ $relationModel = null;
+ } else {
+ $relationModel = $data[$result->$localKey];
+ $relationModel->setParent(clone $result);
+ $relationModel->exists(true);
+ }
+
+ $result->setRelation($relation, $relationModel);
+ }
+
+ /**
+ * 关联模型预查询
+ * @access public
+ * @param array $where 关联预查询条件
+ * @param string $key 关联键名
+ * @param array $subRelation 子关联
+ * @param Closure $closure
+ * @param array $cache 关联缓存
+ * @return array
+ */
+ protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []): array
+ {
+ // 预载入关联查询 支持嵌套预载入
+ $keys = $this->through->where($where)->column($this->throughPk, $this->foreignKey);
+
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ $list = $this->query
+ ->where($this->throughKey, 'in', $keys)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->select();
+
+ // 组装模型数据
+ $data = [];
+ $keys = array_flip($keys);
+
+ foreach ($list as $set) {
+ $data[$keys[$set->{$this->throughKey}]] = $set;
+ }
+
+ return $data;
+ }
+
+}
diff --git a/thinkphp/library/think/model/relation/MorphMany.php b/vendor/topthink/think-orm/src/model/relation/MorphMany.php
old mode 100755
new mode 100644
similarity index 53%
rename from thinkphp/library/think/model/relation/MorphMany.php
rename to vendor/topthink/think-orm/src/model/relation/MorphMany.php
index a1f54889b..82910cbc5
--- a/thinkphp/library/think/model/relation/MorphMany.php
+++ b/vendor/topthink/think-orm/src/model/relation/MorphMany.php
@@ -2,7 +2,7 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
@@ -11,18 +11,35 @@
namespace think\model\relation;
-use think\db\Query;
-use think\Exception;
-use think\Loader;
+use Closure;
+use think\Collection;
+use think\db\BaseQuery as Query;
+use think\db\exception\DbException as Exception;
+use think\helper\Str;
use think\Model;
use think\model\Relation;
+/**
+ * 多态一对多关联
+ */
class MorphMany extends Relation
{
- // 多态字段
+
+ /**
+ * 多态关联外键
+ * @var string
+ */
protected $morphKey;
+ /**
+ * 多态字段名
+ * @var string
+ */
protected $morphType;
- // 多态类型
+
+ /**
+ * 多态类型
+ * @var string
+ */
protected $type;
/**
@@ -34,7 +51,7 @@ class MorphMany extends Relation
* @param string $morphType 多态字段名
* @param string $type 多态类型
*/
- public function __construct(Model $parent, $model, $morphKey, $morphType, $type)
+ public function __construct(Model $parent, string $model, string $morphKey, string $morphType, string $type)
{
$this->parent = $parent;
$this->model = $model;
@@ -47,26 +64,25 @@ class MorphMany extends Relation
/**
* 延迟获取关联数据
* @access public
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包查询条件
- * @return \think\Collection
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
+ * @return Collection
*/
- public function getRelation($subRelation = '', $closure = null)
+ public function getRelation(array $subRelation = [], Closure $closure = null): Collection
{
if ($closure) {
- $closure($this->query);
+ $closure($this->getClosureType($closure));
}
$this->baseQuery();
- $list = $this->query->relation($subRelation)->select();
- $parent = clone $this->parent;
-
- foreach ($list as &$model) {
- $model->setParent($parent);
+ if ($this->withLimit) {
+ $this->query->limit($this->withLimit);
}
- return $list;
+ return $this->query->relation($subRelation)
+ ->select()
+ ->setParent(clone $this->parent);
}
/**
@@ -76,9 +92,10 @@ class MorphMany extends Relation
* @param integer $count 个数
* @param string $id 关联表的统计字段
* @param string $joinType JOIN类型
+ * @param Query $query Query对象
* @return Query
*/
- public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null)
{
throw new Exception('relation not support: has');
}
@@ -86,11 +103,13 @@ class MorphMany extends Relation
/**
* 根据关联条件查询当前模型
* @access public
- * @param mixed $where 查询条件(数组或者闭包)
- * @param mixed $fields 字段
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
* @return Query
*/
- public function hasWhere($where = [], $fields = null)
+ public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null)
{
throw new Exception('relation not support: hasWhere');
}
@@ -98,13 +117,14 @@ class MorphMany extends Relation
/**
* 预载入关联查询
* @access public
- * @param array $resultSet 数据集
- * @param string $relation 当前关联名
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
* @return void
*/
- public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure)
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
{
$morphType = $this->morphType;
$morphKey = $this->morphKey;
@@ -124,10 +144,7 @@ class MorphMany extends Relation
[$morphKey, 'in', $range],
[$morphType, '=', $type],
];
- $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure);
-
- // 关联属性名
- $attr = Loader::parseName($relation);
+ $data = $this->eagerlyMorphToMany($where, $subRelation, $closure, $cache);
// 关联数据封装
foreach ($resultSet as $result) {
@@ -135,12 +152,7 @@ class MorphMany extends Relation
$data[$result->$pk] = [];
}
- foreach ($data[$result->$pk] as &$relationModel) {
- $relationModel->setParent(clone $result);
- $relationModel->isUpdate(true);
- }
-
- $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk]));
+ $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent));
}
}
}
@@ -148,48 +160,43 @@ class MorphMany extends Relation
/**
* 预载入关联查询
* @access public
- * @param Model $result 数据对象
- * @param string $relation 当前关联名
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
* @return void
*/
- public function eagerlyResult(&$result, $relation, $subRelation, $closure)
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
{
$pk = $result->getPk();
if (isset($result->$pk)) {
- $key = $result->$pk;
- $where = [
+ $key = $result->$pk;
+ $data = $this->eagerlyMorphToMany([
[$this->morphKey, '=', $key],
[$this->morphType, '=', $this->type],
- ];
- $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure);
+ ], $subRelation, $closure, $cache);
if (!isset($data[$key])) {
$data[$key] = [];
}
- foreach ($data[$key] as &$relationModel) {
- $relationModel->setParent(clone $result);
- $relationModel->isUpdate(true);
- }
-
- $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$key]));
+ $result->setRelation($relation, $this->resultSetBuild($data[$key], clone $this->parent));
}
}
/**
* 关联统计
* @access public
- * @param Model $result 数据对象
- * @param \Closure $closure 闭包
- * @param string $aggregate 聚合查询方法
- * @param string $field 字段
- * @param string $name 统计字段别名
- * @return integer
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return mixed
*/
- public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '')
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null)
{
$pk = $result->getPk();
@@ -198,11 +205,7 @@ class MorphMany extends Relation
}
if ($closure) {
- $return = $closure($this->query);
-
- if ($return && is_string($return)) {
- $name = $return;
- }
+ $closure($this->getClosureType($closure), $name);
}
return $this->query
@@ -216,20 +219,16 @@ class MorphMany extends Relation
/**
* 获取关联统计子查询
* @access public
- * @param \Closure $closure 闭包
- * @param string $aggregate 聚合查询方法
- * @param string $field 字段
- * @param string $aggregateAlias 聚合字段别名
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
* @return string
*/
- public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '')
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
{
if ($closure) {
- $return = $closure($this->query);
-
- if ($return && is_string($return)) {
- $aggregateAlias = $return;
- }
+ $closure($this->getClosureType($closure), $name);
}
return $this->query
@@ -242,28 +241,39 @@ class MorphMany extends Relation
/**
* 多态一对多 关联模型预查询
* @access protected
- * @param array $where 关联预查询条件
- * @param string $relation 关联名
- * @param string $subRelation 子关联
- * @param \Closure $closure 闭包
+ * @param array $where 关联预查询条件
+ * @param array $subRelation 子关联
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
* @return array
*/
- protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = null)
+ protected function eagerlyMorphToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array
{
// 预载入关联查询 支持嵌套预载入
$this->query->removeOption('where');
if ($closure) {
- $closure($this->query);
+ $this->baseQuery = true;
+ $closure($this->getClosureType($closure));
}
- $list = $this->query->where($where)->with($subRelation)->select();
+ $list = $this->query
+ ->where($where)
+ ->with($subRelation)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->select();
$morphKey = $this->morphKey;
// 组装模型数据
$data = [];
foreach ($list as $set) {
- $data[$set->$morphKey][] = $set;
+ $key = $set->$morphKey;
+
+ if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) {
+ continue;
+ }
+
+ $data[$key][] = $set;
}
return $data;
@@ -272,22 +282,23 @@ class MorphMany extends Relation
/**
* 保存(新增)当前关联数据对象
* @access public
- * @param mixed $data 数据
+ * @param mixed $data 数据 可以使用数组 关联模型对象
+ * @param bool $replace 是否自动识别更新和写入
* @return Model|false
*/
- public function save($data)
+ public function save($data, bool $replace = true)
{
$model = $this->make();
- return $model->save($data) ? $model : false;
+ return $model->replace($replace)->save($data) ? $model : false;
}
/**
* 创建关联对象实例
- * @param array $data
+ * @param array|Model $data
* @return Model
*/
- public function make($data = [])
+ public function make($data = []): Model
{
if ($data instanceof Model) {
$data = $data->getData();
@@ -305,15 +316,16 @@ class MorphMany extends Relation
/**
* 批量保存当前关联数据对象
* @access public
- * @param array $dataSet 数据集
+ * @param iterable $dataSet 数据集
+ * @param boolean $replace 是否自动识别更新和写入
* @return array|false
*/
- public function saveAll(array $dataSet)
+ public function saveAll(iterable $dataSet, bool $replace = true)
{
$result = [];
foreach ($dataSet as $key => $data) {
- $result[] = $this->save($data);
+ $result[] = $this->save($data, $replace);
}
return empty($result) ? false : $result;
@@ -324,7 +336,7 @@ class MorphMany extends Relation
* @access protected
* @return void
*/
- protected function baseQuery()
+ protected function baseQuery(): void
{
if (empty($this->baseQuery) && $this->parent->getData()) {
$pk = $this->parent->getPk();
diff --git a/thinkphp/library/think/model/relation/MorphOne.php b/vendor/topthink/think-orm/src/model/relation/MorphOne.php
old mode 100755
new mode 100644
similarity index 50%
rename from thinkphp/library/think/model/relation/MorphOne.php
rename to vendor/topthink/think-orm/src/model/relation/MorphOne.php
index 775b2dfd7..bc89c0ba1
--- a/thinkphp/library/think/model/relation/MorphOne.php
+++ b/vendor/topthink/think-orm/src/model/relation/MorphOne.php
@@ -2,7 +2,7 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
@@ -11,20 +11,42 @@
namespace think\model\relation;
-use think\db\Query;
-use think\Exception;
-use think\Loader;
+use Closure;
+use think\db\BaseQuery as Query;
+use think\db\exception\DbException as Exception;
+use think\helper\Str;
use think\Model;
use think\model\Relation;
+/**
+ * 多态一对一关联类
+ */
class MorphOne extends Relation
{
- // 多态字段
+ /**
+ * 多态关联外键
+ * @var string
+ */
protected $morphKey;
+
+ /**
+ * 多态字段
+ * @var string
+ */
protected $morphType;
- // 多态类型
+
+ /**
+ * 多态类型
+ * @var string
+ */
protected $type;
+ /**
+ * 绑定的关联属性
+ * @var array
+ */
+ protected $bindAttr = [];
+
/**
* 构造函数
* @access public
@@ -34,7 +56,7 @@ class MorphOne extends Relation
* @param string $morphType 多态字段名
* @param string $type 多态类型
*/
- public function __construct(Model $parent, $model, $morphKey, $morphType, $type)
+ public function __construct(Model $parent, string $model, string $morphKey, string $morphType, string $type)
{
$this->parent = $parent;
$this->model = $model;
@@ -47,14 +69,14 @@ class MorphOne extends Relation
/**
* 延迟获取关联数据
* @access public
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包查询条件
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
* @return Model
*/
- public function getRelation($subRelation = '', $closure = null)
+ public function getRelation(array $subRelation = [], Closure $closure = null)
{
if ($closure) {
- $closure($this->query);
+ $closure($this->getClosureType($closure));
}
$this->baseQuery();
@@ -62,6 +84,11 @@ class MorphOne extends Relation
$relationModel = $this->query->relation($subRelation)->find();
if ($relationModel) {
+ if (!empty($this->bindAttr)) {
+ // 绑定关联属性
+ $this->bindAttr($this->parent, $relationModel);
+ }
+
$relationModel->setParent(clone $this->parent);
}
@@ -75,9 +102,10 @@ class MorphOne extends Relation
* @param integer $count 个数
* @param string $id 关联表的统计字段
* @param string $joinType JOIN类型
+ * @param Query $query Query对象
* @return Query
*/
- public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null)
{
return $this->parent;
}
@@ -85,11 +113,13 @@ class MorphOne extends Relation
/**
* 根据关联条件查询当前模型
* @access public
- * @param mixed $where 查询条件(数组或者闭包)
- * @param mixed $fields 字段
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
* @return Query
*/
- public function hasWhere($where = [], $fields = null)
+ public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null)
{
throw new Exception('relation not support: hasWhere');
}
@@ -97,13 +127,14 @@ class MorphOne extends Relation
/**
* 预载入关联查询
* @access public
- * @param array $resultSet 数据集
- * @param string $relation 当前关联名
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
* @return void
*/
- public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure)
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
{
$morphType = $this->morphType;
$morphKey = $this->morphKey;
@@ -122,10 +153,7 @@ class MorphOne extends Relation
$data = $this->eagerlyMorphToOne([
[$morphKey, 'in', $range],
[$morphType, '=', $type],
- ], $relation, $subRelation, $closure);
-
- // 关联属性名
- $attr = Loader::parseName($relation);
+ ], $subRelation, $closure, $cache);
// 关联数据封装
foreach ($resultSet as $result) {
@@ -134,10 +162,16 @@ class MorphOne extends Relation
} else {
$relationModel = $data[$result->$pk];
$relationModel->setParent(clone $result);
- $relationModel->isUpdate(true);
+ $relationModel->exists(true);
}
- $result->setRelation($attr, $relationModel);
+ if (!empty($this->bindAttr)) {
+ // 绑定关联属性
+ $this->bindAttr($result, $relationModel);
+ } else {
+ // 设置关联属性
+ $result->setRelation($relation, $relationModel);
+ }
}
}
}
@@ -145,13 +179,14 @@ class MorphOne extends Relation
/**
* 预载入关联查询
* @access public
- * @param Model $result 数据对象
- * @param string $relation 当前关联名
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
* @return void
*/
- public function eagerlyResult(&$result, $relation, $subRelation, $closure)
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
{
$pk = $result->getPk();
@@ -160,37 +195,48 @@ class MorphOne extends Relation
$data = $this->eagerlyMorphToOne([
[$this->morphKey, '=', $pk],
[$this->morphType, '=', $this->type],
- ], $relation, $subRelation, $closure);
+ ], $subRelation, $closure, $cache);
if (isset($data[$pk])) {
$relationModel = $data[$pk];
$relationModel->setParent(clone $result);
- $relationModel->isUpdate(true);
+ $relationModel->exists(true);
} else {
$relationModel = null;
}
- $result->setRelation(Loader::parseName($relation), $relationModel);
+ if (!empty($this->bindAttr)) {
+ // 绑定关联属性
+ $this->bindAttr($result, $relationModel);
+ } else {
+ // 设置关联属性
+ $result->setRelation($relation, $relationModel);
+ }
}
}
/**
* 多态一对一 关联模型预查询
* @access protected
- * @param array $where 关联预查询条件
- * @param string $relation 关联名
- * @param string $subRelation 子关联
- * @param \Closure $closure 闭包
+ * @param array $where 关联预查询条件
+ * @param array $subRelation 子关联
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
* @return array
*/
- protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = null)
+ protected function eagerlyMorphToOne(array $where, array $subRelation = [], $closure = null, array $cache = []): array
{
// 预载入关联查询 支持嵌套预载入
if ($closure) {
- $closure($this->query);
+ $this->baseQuery = true;
+ $closure($this->getClosureType($closure));
}
- $list = $this->query->where($where)->with($subRelation)->select();
+ $list = $this->query
+ ->where($where)
+ ->with($subRelation)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->select();
$morphKey = $this->morphKey;
// 组装模型数据
@@ -206,21 +252,22 @@ class MorphOne extends Relation
/**
* 保存(新增)当前关联数据对象
* @access public
- * @param mixed $data 数据
+ * @param mixed $data 数据 可以使用数组 关联模型对象
+ * @param boolean $replace 是否自动识别更新和写入
* @return Model|false
*/
- public function save($data)
+ public function save($data, bool $replace = true)
{
$model = $this->make();
- return $model->save($data) ? $model : false;
+ return $model->replace($replace)->save($data) ? $model : false;
}
/**
* 创建关联对象实例
- * @param array $data
+ * @param array|Model $data
* @return Model
*/
- public function make($data = [])
+ public function make($data = []): Model
{
if ($data instanceof Model) {
$data = $data->getData();
@@ -240,7 +287,7 @@ class MorphOne extends Relation
* @access protected
* @return void
*/
- protected function baseQuery()
+ protected function baseQuery(): void
{
if (empty($this->baseQuery) && $this->parent->getData()) {
$pk = $this->parent->getPk();
@@ -253,4 +300,48 @@ class MorphOne extends Relation
}
}
+ /**
+ * 绑定关联表的属性到父模型属性
+ * @access public
+ * @param array $attr 要绑定的属性列表
+ * @return $this
+ */
+ public function bind(array $attr)
+ {
+ $this->bindAttr = $attr;
+
+ return $this;
+ }
+
+ /**
+ * 获取绑定属性
+ * @access public
+ * @return array
+ */
+ public function getBindAttr(): array
+ {
+ return $this->bindAttr;
+ }
+
+ /**
+ * 绑定关联属性到父模型
+ * @access protected
+ * @param Model $result 父模型对象
+ * @param Model $model 关联模型对象
+ * @return void
+ * @throws Exception
+ */
+ protected function bindAttr(Model $result, Model $model = null): void
+ {
+ foreach ($this->bindAttr as $key => $attr) {
+ $key = is_numeric($key) ? $attr : $key;
+ $value = $result->getOrigin($key);
+
+ if (!is_null($value)) {
+ throw new Exception('bind attr has exists:' . $key);
+ }
+
+ $result->setAttr($key, $model ? $model->$attr : null);
+ }
+ }
}
diff --git a/thinkphp/library/think/model/relation/MorphTo.php b/vendor/topthink/think-orm/src/model/relation/MorphTo.php
old mode 100755
new mode 100644
similarity index 62%
rename from thinkphp/library/think/model/relation/MorphTo.php
rename to vendor/topthink/think-orm/src/model/relation/MorphTo.php
index 6da6a0252..eaa98902a
--- a/thinkphp/library/think/model/relation/MorphTo.php
+++ b/vendor/topthink/think-orm/src/model/relation/MorphTo.php
@@ -1,307 +1,332 @@
-
-// +----------------------------------------------------------------------
-
-namespace think\model\relation;
-
-use think\Exception;
-use think\Loader;
-use think\Model;
-use think\model\Relation;
-
-class MorphTo extends Relation
-{
- // 多态字段
- protected $morphKey;
- protected $morphType;
- // 多态别名
- protected $alias;
- // 关联名
- protected $relation;
-
- /**
- * 架构函数
- * @access public
- * @param Model $parent 上级模型对象
- * @param string $morphType 多态字段名
- * @param string $morphKey 外键名
- * @param array $alias 多态别名定义
- * @param string $relation 关联名
- */
- public function __construct(Model $parent, $morphType, $morphKey, $alias = [], $relation = null)
- {
- $this->parent = $parent;
- $this->morphType = $morphType;
- $this->morphKey = $morphKey;
- $this->alias = $alias;
- $this->relation = $relation;
- }
-
- /**
- * 获取当前的关联模型类的实例
- * @access public
- * @return Model
- */
- public function getModel()
- {
- $morphType = $this->morphType;
- $model = $this->parseModel($this->parent->$morphType);
-
- return (new $model);
- }
-
- /**
- * 延迟获取关联数据
- * @access public
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包查询条件
- * @return Model
- */
- public function getRelation($subRelation = '', $closure = null)
- {
- $morphKey = $this->morphKey;
- $morphType = $this->morphType;
-
- // 多态模型
- $model = $this->parseModel($this->parent->$morphType);
-
- // 主键数据
- $pk = $this->parent->$morphKey;
-
- $relationModel = (new $model)->relation($subRelation)->find($pk);
-
- if ($relationModel) {
- $relationModel->setParent(clone $this->parent);
- }
-
- return $relationModel;
- }
-
- /**
- * 根据关联条件查询当前模型
- * @access public
- * @param string $operator 比较操作符
- * @param integer $count 个数
- * @param string $id 关联表的统计字段
- * @param string $joinType JOIN类型
- * @return Query
- */
- public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
- {
- return $this->parent;
- }
-
- /**
- * 根据关联条件查询当前模型
- * @access public
- * @param mixed $where 查询条件(数组或者闭包)
- * @param mixed $fields 字段
- * @return Query
- */
- public function hasWhere($where = [], $fields = null)
- {
- throw new Exception('relation not support: hasWhere');
- }
-
- /**
- * 解析模型的完整命名空间
- * @access protected
- * @param string $model 模型名(或者完整类名)
- * @return string
- */
- protected function parseModel($model)
- {
- if (isset($this->alias[$model])) {
- $model = $this->alias[$model];
- }
-
- if (false === strpos($model, '\\')) {
- $path = explode('\\', get_class($this->parent));
- array_pop($path);
- array_push($path, Loader::parseName($model, 1));
- $model = implode('\\', $path);
- }
-
- return $model;
- }
-
- /**
- * 设置多态别名
- * @access public
- * @param array $alias 别名定义
- * @return $this
- */
- public function setAlias($alias)
- {
- $this->alias = $alias;
-
- return $this;
- }
-
- /**
- * 移除关联查询参数
- * @access public
- * @return $this
- */
- public function removeOption()
- {
- return $this;
- }
-
- /**
- * 预载入关联查询
- * @access public
- * @param array $resultSet 数据集
- * @param string $relation 当前关联名
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包
- * @return void
- * @throws Exception
- */
- public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure)
- {
- $morphKey = $this->morphKey;
- $morphType = $this->morphType;
- $range = [];
-
- foreach ($resultSet as $result) {
- // 获取关联外键列表
- if (!empty($result->$morphKey)) {
- $range[$result->$morphType][] = $result->$morphKey;
- }
- }
-
- if (!empty($range)) {
- // 关联属性名
- $attr = Loader::parseName($relation);
-
- foreach ($range as $key => $val) {
- // 多态类型映射
- $model = $this->parseModel($key);
- $obj = new $model;
- $pk = $obj->getPk();
- $list = $obj->all($val, $subRelation);
- $data = [];
-
- foreach ($list as $k => $vo) {
- $data[$vo->$pk] = $vo;
- }
-
- foreach ($resultSet as $result) {
- if ($key == $result->$morphType) {
- // 关联模型
- if (!isset($data[$result->$morphKey])) {
- $relationModel = null;
- } else {
- $relationModel = $data[$result->$morphKey];
- $relationModel->setParent(clone $result);
- $relationModel->isUpdate(true);
- }
-
- $result->setRelation($attr, $relationModel);
- }
- }
- }
- }
- }
-
- /**
- * 预载入关联查询
- * @access public
- * @param Model $result 数据对象
- * @param string $relation 当前关联名
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包
- * @return void
- */
- public function eagerlyResult(&$result, $relation, $subRelation, $closure)
- {
- $morphKey = $this->morphKey;
- $morphType = $this->morphType;
- // 多态类型映射
- $model = $this->parseModel($result->{$this->morphType});
-
- $this->eagerlyMorphToOne($model, $relation, $result, $subRelation);
- }
-
- /**
- * 关联统计
- * @access public
- * @param Model $result 数据对象
- * @param \Closure $closure 闭包
- * @param string $aggregate 聚合查询方法
- * @param string $field 字段
- * @param string $name 统计字段别名
- * @return integer
- */
- public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '')
- {}
-
- /**
- * 多态MorphTo 关联模型预查询
- * @access protected
- * @param string $model 关联模型对象
- * @param string $relation 关联名
- * @param Model $result
- * @param string $subRelation 子关联
- * @return void
- */
- protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '')
- {
- // 预载入关联查询 支持嵌套预载入
- $pk = $this->parent->{$this->morphKey};
- $data = (new $model)->with($subRelation)->find($pk);
-
- if ($data) {
- $data->setParent(clone $result);
- $data->isUpdate(true);
- }
-
- $result->setRelation(Loader::parseName($relation), $data ?: null);
- }
-
- /**
- * 添加关联数据
- * @access public
- * @param Model $model 关联模型对象
- * @param string $type 多态类型
- * @return Model
- */
- public function associate($model, $type = '')
- {
- $morphKey = $this->morphKey;
- $morphType = $this->morphType;
- $pk = $model->getPk();
-
- $this->parent->setAttr($morphKey, $model->$pk);
- $this->parent->setAttr($morphType, $type ?: get_class($model));
- $this->parent->save();
-
- return $this->parent->setRelation($this->relation, $model);
- }
-
- /**
- * 注销关联数据
- * @access public
- * @return Model
- */
- public function dissociate()
- {
- $morphKey = $this->morphKey;
- $morphType = $this->morphType;
-
- $this->parent->setAttr($morphKey, null);
- $this->parent->setAttr($morphType, null);
- $this->parent->save();
-
- return $this->parent->setRelation($this->relation, null);
- }
-
-}
+
+// +----------------------------------------------------------------------
+
+namespace think\model\relation;
+
+use Closure;
+use think\db\exception\DbException as Exception;
+use think\helper\Str;
+use think\Model;
+use think\model\Relation;
+
+/**
+ * 多态关联类
+ */
+class MorphTo extends Relation
+{
+ /**
+ * 多态关联外键
+ * @var string
+ */
+ protected $morphKey;
+
+ /**
+ * 多态字段
+ * @var string
+ */
+ protected $morphType;
+
+ /**
+ * 多态别名
+ * @var array
+ */
+ protected $alias = [];
+
+ /**
+ * 关联名
+ * @var string
+ */
+ protected $relation;
+
+ /**
+ * 架构函数
+ * @access public
+ * @param Model $parent 上级模型对象
+ * @param string $morphType 多态字段名
+ * @param string $morphKey 外键名
+ * @param array $alias 多态别名定义
+ * @param string $relation 关联名
+ */
+ public function __construct(Model $parent, string $morphType, string $morphKey, array $alias = [], string $relation = null)
+ {
+ $this->parent = $parent;
+ $this->morphType = $morphType;
+ $this->morphKey = $morphKey;
+ $this->alias = $alias;
+ $this->relation = $relation;
+ }
+
+ /**
+ * 获取当前的关联模型类的实例
+ * @access public
+ * @return Model
+ */
+ public function getModel(): Model
+ {
+ $morphType = $this->morphType;
+ $model = $this->parseModel($this->parent->$morphType);
+
+ return (new $model);
+ }
+
+ /**
+ * 延迟获取关联数据
+ * @access public
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
+ * @return Model
+ */
+ public function getRelation(array $subRelation = [], Closure $closure = null)
+ {
+ $morphKey = $this->morphKey;
+ $morphType = $this->morphType;
+
+ // 多态模型
+ $model = $this->parseModel($this->parent->$morphType);
+
+ // 主键数据
+ $pk = $this->parent->$morphKey;
+
+ $relationModel = (new $model)->relation($subRelation)->find($pk);
+
+ if ($relationModel) {
+ $relationModel->setParent(clone $this->parent);
+ }
+
+ return $relationModel;
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null)
+ {
+ return $this->parent;
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null)
+ {
+ throw new Exception('relation not support: hasWhere');
+ }
+
+ /**
+ * 解析模型的完整命名空间
+ * @access protected
+ * @param string $model 模型名(或者完整类名)
+ * @return string
+ */
+ protected function parseModel(string $model): string
+ {
+ if (isset($this->alias[$model])) {
+ $model = $this->alias[$model];
+ }
+
+ if (false === strpos($model, '\\')) {
+ $path = explode('\\', get_class($this->parent));
+ array_pop($path);
+ array_push($path, Str::studly($model));
+ $model = implode('\\', $path);
+ }
+
+ return $model;
+ }
+
+ /**
+ * 设置多态别名
+ * @access public
+ * @param array $alias 别名定义
+ * @return $this
+ */
+ public function setAlias(array $alias)
+ {
+ $this->alias = $alias;
+
+ return $this;
+ }
+
+ /**
+ * 移除关联查询参数
+ * @access public
+ * @return $this
+ */
+ public function removeOption()
+ {
+ return $this;
+ }
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ * @throws Exception
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
+ {
+ $morphKey = $this->morphKey;
+ $morphType = $this->morphType;
+ $range = [];
+
+ foreach ($resultSet as $result) {
+ // 获取关联外键列表
+ if (!empty($result->$morphKey)) {
+ $range[$result->$morphType][] = $result->$morphKey;
+ }
+ }
+
+ if (!empty($range)) {
+
+ foreach ($range as $key => $val) {
+ // 多态类型映射
+ $model = $this->parseModel($key);
+ $obj = new $model;
+ $pk = $obj->getPk();
+ $list = $obj->with($subRelation)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->select($val);
+ $data = [];
+
+ foreach ($list as $k => $vo) {
+ $data[$vo->$pk] = $vo;
+ }
+
+ foreach ($resultSet as $result) {
+ if ($key == $result->$morphType) {
+ // 关联模型
+ if (!isset($data[$result->$morphKey])) {
+ $relationModel = null;
+ } else {
+ $relationModel = $data[$result->$morphKey];
+ $relationModel->setParent(clone $result);
+ $relationModel->exists(true);
+ }
+
+ $result->setRelation($relation, $relationModel);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ // 多态类型映射
+ $model = $this->parseModel($result->{$this->morphType});
+
+ $this->eagerlyMorphToOne($model, $relation, $result, $subRelation, $cache);
+ }
+
+ /**
+ * 关联统计
+ * @access public
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @return integer
+ */
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*')
+ {}
+
+ /**
+ * 多态MorphTo 关联模型预查询
+ * @access protected
+ * @param string $model 关联模型对象
+ * @param string $relation 关联名
+ * @param Model $result
+ * @param array $subRelation 子关联
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ protected function eagerlyMorphToOne(string $model, string $relation, Model $result, array $subRelation = [], array $cache = []): void
+ {
+ // 预载入关联查询 支持嵌套预载入
+ $pk = $this->parent->{$this->morphKey};
+ $data = (new $model)->with($subRelation)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->find($pk);
+
+ if ($data) {
+ $data->setParent(clone $result);
+ $data->exists(true);
+ }
+
+ $result->setRelation($relation, $data ?: null);
+ }
+
+ /**
+ * 添加关联数据
+ * @access public
+ * @param Model $model 关联模型对象
+ * @param string $type 多态类型
+ * @return Model
+ */
+ public function associate(Model $model, string $type = ''): Model
+ {
+ $morphKey = $this->morphKey;
+ $morphType = $this->morphType;
+ $pk = $model->getPk();
+
+ $this->parent->setAttr($morphKey, $model->$pk);
+ $this->parent->setAttr($morphType, $type ?: get_class($model));
+ $this->parent->save();
+
+ return $this->parent->setRelation($this->relation, $model);
+ }
+
+ /**
+ * 注销关联数据
+ * @access public
+ * @return Model
+ */
+ public function dissociate(): Model
+ {
+ $morphKey = $this->morphKey;
+ $morphType = $this->morphType;
+
+ $this->parent->setAttr($morphKey, null);
+ $this->parent->setAttr($morphType, null);
+ $this->parent->save();
+
+ return $this->parent->setRelation($this->relation, null);
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/model/relation/MorphToMany.php b/vendor/topthink/think-orm/src/model/relation/MorphToMany.php
new file mode 100644
index 000000000..88bbf9aa7
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/relation/MorphToMany.php
@@ -0,0 +1,458 @@
+
+// +----------------------------------------------------------------------
+
+namespace think\model\relation;
+
+use Closure;
+use Exception;
+use think\db\BaseQuery as Query;
+use think\db\Raw;
+use think\Model;
+use think\model\Pivot;
+
+/**
+ * 多态多对多关联
+ */
+class MorphToMany extends BelongsToMany
+{
+
+ /**
+ * 多态字段名
+ * @var string
+ */
+ protected $morphType;
+
+ /**
+ * 多态模型名
+ * @var string
+ */
+ protected $morphClass;
+
+ /**
+ * 是否反向关联
+ * @var bool
+ */
+ protected $inverse;
+
+ /**
+ * 架构函数
+ * @access public
+ * @param Model $parent 上级模型对象
+ * @param string $model 模型名
+ * @param string $middle 中间表名/模型名
+ * @param string $morphKey 关联外键
+ * @param string $morphType 多态字段名
+ * @param string $localKey 当前模型关联键
+ * @param bool $inverse 反向关联
+ */
+ public function __construct(Model $parent, string $model, string $middle, string $morphType, string $morphKey, string $localKey, bool $inverse = false)
+ {
+ $this->morphType = $morphType;
+ $this->inverse = $inverse;
+ $this->morphClass = $inverse ? $model : get_class($parent);
+
+ $foreignKey = $inverse ? $morphKey : $localKey;
+ $localKey = $inverse ? $localKey : $morphKey;
+
+ parent::__construct($parent, $model, $middle, $foreignKey, $localKey);
+ }
+
+ /**
+ * 预载入关联查询(数据集)
+ * @access public
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
+ {
+ $pk = $resultSet[0]->getPk();
+ $range = [];
+
+ foreach ($resultSet as $result) {
+ // 获取关联外键列表
+ if (isset($result->$pk)) {
+ $range[] = $result->$pk;
+ }
+ }
+
+ if (!empty($range)) {
+ // 查询关联数据
+ $data = $this->eagerlyManyToMany([
+ ['pivot.' . $this->localKey, 'in', $range],
+ ['pivot.' . $this->morphType, '=', $this->morphClass],
+ ], $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ foreach ($resultSet as $result) {
+ if (!isset($data[$result->$pk])) {
+ $data[$result->$pk] = [];
+ }
+
+ $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent));
+ }
+ }
+ }
+
+ /**
+ * 预载入关联查询(单个数据)
+ * @access public
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
+ {
+ $pk = $result->getPk();
+
+ if (isset($result->$pk)) {
+ $pk = $result->$pk;
+ // 查询管理数据
+ $data = $this->eagerlyManyToMany([
+ ['pivot.' . $this->localKey, '=', $pk],
+ ['pivot.' . $this->morphType, '=', $this->morphClass],
+ ], $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ if (!isset($data[$pk])) {
+ $data[$pk] = [];
+ }
+
+ $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent));
+ }
+ }
+
+ /**
+ * 关联统计
+ * @access public
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return integer
+ */
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): float
+ {
+ $pk = $result->getPk();
+
+ if (!isset($result->$pk)) {
+ return 0;
+ }
+
+ $pk = $result->$pk;
+
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
+ ['pivot.' . $this->localKey, '=', $pk],
+ ['pivot.' . $this->morphType, '=', $this->morphClass],
+ ])->$aggregate($field);
+ }
+
+ /**
+ * 获取关联统计子查询
+ * @access public
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return string
+ */
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
+ ['pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->db(false)->getTable() . '.' . $this->parent->getPk())],
+ ['pivot.' . $this->morphType, '=', $this->morphClass],
+ ])->fetchSql()->$aggregate($field);
+ }
+
+ /**
+ * BELONGS TO MANY 关联查询
+ * @access protected
+ * @param string $foreignKey 关联模型关联键
+ * @param string $localKey 当前模型关联键
+ * @param array $condition 关联查询条件
+ * @return Query
+ */
+ protected function belongsToManyQuery(string $foreignKey, string $localKey, array $condition = []): Query
+ {
+ // 关联查询封装
+ $tableName = $this->query->getTable();
+ $table = $this->pivot->db()->getTable();
+ $fields = $this->getQueryFields($tableName);
+
+ if ($this->withLimit) {
+ $this->query->limit($this->withLimit);
+ }
+
+ $query = $this->query
+ ->field($fields)
+ ->tableField(true, $table, 'pivot', 'pivot__');
+
+ if (empty($this->baseQuery)) {
+ $relationFk = $this->query->getPk();
+ $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk)
+ ->where($condition);
+ }
+
+ return $query;
+ }
+
+ /**
+ * 多对多 关联模型预查询
+ * @access protected
+ * @param array $where 关联预查询条件
+ * @param array $subRelation 子关联
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return array
+ */
+ protected function eagerlyManyToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ // 预载入关联查询 支持嵌套预载入
+ $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)
+ ->with($subRelation)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->select();
+
+ // 组装模型数据
+ $data = [];
+ foreach ($list as $set) {
+ $pivot = [];
+ foreach ($set->getData() as $key => $val) {
+ if (strpos($key, '__')) {
+ [$name, $attr] = explode('__', $key, 2);
+ if ('pivot' == $name) {
+ $pivot[$attr] = $val;
+ unset($set->$key);
+ }
+ }
+ }
+
+ $key = $pivot[$this->localKey];
+
+ if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) {
+ continue;
+ }
+
+ $set->setRelation($this->pivotDataName, $this->newPivot($pivot));
+
+ $data[$key][] = $set;
+ }
+
+ return $data;
+ }
+
+ /**
+ * 附加关联的一个中间表数据
+ * @access public
+ * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键
+ * @param array $pivot 中间表额外数据
+ * @return array|Pivot
+ */
+ public function attach($data, array $pivot = [])
+ {
+ if (is_array($data)) {
+ if (key($data) === 0) {
+ $id = $data;
+ } else {
+ // 保存关联表数据
+ $model = new $this->model;
+ $id = $model->insertGetId($data);
+ }
+ } else if (is_numeric($data) || is_string($data)) {
+ // 根据关联表主键直接写入中间表
+ $id = $data;
+ } else if ($data instanceof Model) {
+ // 根据关联表主键直接写入中间表
+ $id = $data->getKey();
+ }
+
+ if (!empty($id)) {
+ // 保存中间表数据
+ $pivot[$this->localKey] = $this->parent->getKey();
+ $pivot[$this->morphType] = $this->morphClass;
+ $ids = (array) $id;
+
+ $result = [];
+
+ foreach ($ids as $id) {
+ $pivot[$this->foreignKey] = $id;
+
+ $this->pivot->replace()
+ ->exists(false)
+ ->data([])
+ ->save($pivot);
+ $result[] = $this->newPivot($pivot);
+ }
+
+ if (count($result) == 1) {
+ // 返回中间表模型对象
+ $result = $result[0];
+ }
+
+ return $result;
+ } else {
+ throw new Exception('miss relation data');
+ }
+ }
+
+ /**
+ * 判断是否存在关联数据
+ * @access public
+ * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键
+ * @return Pivot|false
+ */
+ public function attached($data)
+ {
+ if ($data instanceof Model) {
+ $id = $data->getKey();
+ } else {
+ $id = $data;
+ }
+
+ $pivot = $this->pivot
+ ->where($this->localKey, $this->parent->getKey())
+ ->where($this->morphType, $this->morphClass)
+ ->where($this->foreignKey, $id)
+ ->find();
+
+ return $pivot ?: false;
+ }
+
+ /**
+ * 解除关联的一个中间表数据
+ * @access public
+ * @param integer|array $data 数据 可以使用关联对象的主键
+ * @param bool $relationDel 是否同时删除关联表数据
+ * @return integer
+ */
+ public function detach($data = null, bool $relationDel = false): int
+ {
+ if (is_array($data)) {
+ $id = $data;
+ } else if (is_numeric($data) || is_string($data)) {
+ // 根据关联表主键直接写入中间表
+ $id = $data;
+ } else if ($data instanceof Model) {
+ // 根据关联表主键直接写入中间表
+ $id = $data->getKey();
+ }
+
+ // 删除中间表数据
+ $pivot = [
+ [$this->localKey, '=', $this->parent->getKey()],
+ [$this->morphType, '=', $this->morphClass],
+ ];
+
+ if (isset($id)) {
+ $pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id];
+ }
+
+ $result = $this->pivot->where($pivot)->delete();
+
+ // 删除关联表数据
+ if (isset($id) && $relationDel) {
+ $model = $this->model;
+ $model::destroy($id);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 数据同步
+ * @access public
+ * @param array $ids
+ * @param bool $detaching
+ * @return array
+ */
+ public function sync(array $ids, bool $detaching = true): array
+ {
+ $changes = [
+ 'attached' => [],
+ 'detached' => [],
+ 'updated' => [],
+ ];
+
+ $current = $this->pivot
+ ->where($this->localKey, $this->parent->getKey())
+ ->where($this->morphType, $this->morphClass)
+ ->column($this->foreignKey);
+
+ $records = [];
+
+ foreach ($ids as $key => $value) {
+ if (!is_array($value)) {
+ $records[$value] = [];
+ } else {
+ $records[$key] = $value;
+ }
+ }
+
+ $detach = array_diff($current, array_keys($records));
+
+ if ($detaching && count($detach) > 0) {
+ $this->detach($detach);
+ $changes['detached'] = $detach;
+ }
+
+ foreach ($records as $id => $attributes) {
+ if (!in_array($id, $current)) {
+ $this->attach($id, $attributes);
+ $changes['attached'][] = $id;
+ } else if (count($attributes) > 0 && $this->attach($id, $attributes)) {
+ $changes['updated'][] = $id;
+ }
+ }
+
+ return $changes;
+ }
+
+ /**
+ * 执行基础查询(仅执行一次)
+ * @access protected
+ * @return void
+ */
+ protected function baseQuery(): void
+ {
+ if (empty($this->baseQuery)) {
+ $foreignKey = $this->foreignKey;
+ $localKey = $this->localKey;
+
+ // 关联查询
+ $this->belongsToManyQuery($foreignKey, $localKey, [
+ ['pivot.' . $localKey, '=', $this->parent->getKey()],
+ ['pivot.' . $this->morphType, '=', $this->morphClass],
+ ]);
+
+ $this->baseQuery = true;
+ }
+ }
+
+}
diff --git a/thinkphp/library/think/model/relation/OneToOne.php b/vendor/topthink/think-orm/src/model/relation/OneToOne.php
old mode 100755
new mode 100644
similarity index 53%
rename from thinkphp/library/think/model/relation/OneToOne.php
rename to vendor/topthink/think-orm/src/model/relation/OneToOne.php
index 59fce77fb..65bbfdae8
--- a/thinkphp/library/think/model/relation/OneToOne.php
+++ b/vendor/topthink/think-orm/src/model/relation/OneToOne.php
@@ -2,7 +2,7 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
@@ -11,26 +11,35 @@
namespace think\model\relation;
-use think\db\Query;
-use think\Exception;
-use think\Loader;
+use Closure;
+use think\db\BaseQuery as Query;
+use think\db\exception\DbException as Exception;
+use think\helper\Str;
use think\Model;
use think\model\Relation;
/**
- * Class OneToOne
+ * 一对一关联基础类
* @package think\model\relation
- *
*/
abstract class OneToOne extends Relation
{
- // 预载入方式 0 -JOIN 1 -IN
- protected $eagerlyType = 1;
- // 当前关联的JOIN类型
- protected $joinType;
- // 要绑定的属性
+ /**
+ * JOIN类型
+ * @var string
+ */
+ protected $joinType = 'INNER';
+
+ /**
+ * 绑定的关联属性
+ * @var array
+ */
protected $bindAttr = [];
- // 关联名
+
+ /**
+ * 关联名
+ * @var string
+ */
protected $relation;
/**
@@ -39,7 +48,7 @@ abstract class OneToOne extends Relation
* @param string $type JOIN类型
* @return $this
*/
- public function joinType($type)
+ public function joinType(string $type)
{
$this->joinType = $type;
return $this;
@@ -48,17 +57,17 @@ abstract class OneToOne extends Relation
/**
* 预载入关联查询(JOIN方式)
* @access public
- * @param Query $query 查询对象
- * @param string $relation 关联名
- * @param mixed $field 关联字段
- * @param string $joinType JOIN方式
- * @param \Closure $closure 闭包条件
- * @param bool $first
+ * @param Query $query 查询对象
+ * @param string $relation 关联名
+ * @param mixed $field 关联字段
+ * @param string $joinType JOIN方式
+ * @param Closure $closure 闭包条件
+ * @param bool $first
* @return void
*/
- public function eagerly(Query $query, $relation, $field, $joinType, $closure, $first)
+ public function eagerly(Query $query, string $relation, $field = true, string $joinType = '', Closure $closure = null, bool $first = false): void
{
- $name = Loader::parseName(basename(str_replace('\\', '/', get_class($this->parent))));
+ $name = Str::snake(class_basename($this->parent));
if ($first) {
$table = $query->getTable();
@@ -71,7 +80,7 @@ abstract class OneToOne extends Relation
$masterField = true;
}
- $query->field($masterField, false, $table, $name);
+ $query->tableField($masterField, $table, $name);
}
// 预载入封装
@@ -89,92 +98,94 @@ abstract class OneToOne extends Relation
if ($closure) {
// 执行闭包查询
- $closure($query);
- // 使用withField指定获取关联的字段,如
- // $query->where(['id'=>1])->withField('id,name');
- if ($query->getOptions('with_field')) {
- $field = $query->getOptions('with_field');
- $query->removeOption('with_field');
+ $closure($this->getClosureType($closure));
+
+ // 使用withField指定获取关联的字段
+ if ($this->withField) {
+ $field = $this->withField;
}
}
$query->join([$joinTable => $joinAlias], $joinOn, $joinType)
- ->field($field, false, $joinTable, $joinAlias, $relation . '__');
+ ->tableField($field, $joinTable, $joinAlias, $relation . '__');
}
/**
* 预载入关联查询(数据集)
* @access protected
- * @param array $resultSet
- * @param string $relation
- * @param string $subRelation
- * @param \Closure $closure
+ * @param array $resultSet
+ * @param string $relation
+ * @param array $subRelation
+ * @param Closure $closure
* @return mixed
*/
- abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure);
+ abstract protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null);
/**
* 预载入关联查询(数据)
* @access protected
- * @param Model $result
- * @param string $relation
- * @param string $subRelation
- * @param \Closure $closure
+ * @param Model $result
+ * @param string $relation
+ * @param array $subRelation
+ * @param Closure $closure
* @return mixed
*/
- abstract protected function eagerlyOne(&$result, $relation, $subRelation, $closure);
+ abstract protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null);
/**
* 预载入关联查询(数据集)
* @access public
- * @param array $resultSet 数据集
- * @param string $relation 当前关联名
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包
- * @param bool $join 是否为JOIN方式
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @param bool $join 是否为JOIN方式
* @return void
*/
- public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $join = false)
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = [], bool $join = false): void
{
- if ($join || 0 == $this->eagerlyType) {
+ if ($join) {
// 模型JOIN关联组装
foreach ($resultSet as $result) {
$this->match($this->model, $relation, $result);
}
} else {
// IN查询
- $this->eagerlySet($resultSet, $relation, $subRelation, $closure);
+ $this->eagerlySet($resultSet, $relation, $subRelation, $closure, $cache);
}
}
/**
* 预载入关联查询(数据)
* @access public
- * @param Model $result 数据对象
- * @param string $relation 当前关联名
- * @param string $subRelation 子关联名
- * @param \Closure $closure 闭包
- * @param bool $join 是否为JOIN方式
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @param bool $join 是否为JOIN方式
* @return void
*/
- public function eagerlyResult(&$result, $relation, $subRelation, $closure, $join = false)
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = [], bool $join = false): void
{
- if (0 == $this->eagerlyType || $join) {
+ if ($join) {
// 模型JOIN关联组装
$this->match($this->model, $relation, $result);
} else {
// IN查询
- $this->eagerlyOne($result, $relation, $subRelation, $closure);
+ $this->eagerlyOne($result, $relation, $subRelation, $closure, $cache);
}
}
/**
* 保存(新增)当前关联数据对象
* @access public
- * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键
+ * @param mixed $data 数据 可以使用数组 关联模型对象
+ * @param boolean $replace 是否自动识别更新和写入
* @return Model|false
*/
- public function save($data)
+ public function save($data, bool $replace = true)
{
if ($data instanceof Model) {
$data = $data->getData();
@@ -184,43 +195,17 @@ abstract class OneToOne extends Relation
// 保存关联表数据
$data[$this->foreignKey] = $this->parent->{$this->localKey};
- return $model->save($data) ? $model : false;
- }
-
- /**
- * 设置预载入方式
- * @access public
- * @param integer $type 预载入方式 0 JOIN查询 1 IN查询
- * @return $this
- */
- public function setEagerlyType($type)
- {
- $this->eagerlyType = $type;
-
- return $this;
- }
-
- /**
- * 获取预载入方式
- * @access public
- * @return integer
- */
- public function getEagerlyType()
- {
- return $this->eagerlyType;
+ return $model->replace($replace)->save($data) ? $model : false;
}
/**
* 绑定关联表的属性到父模型属性
* @access public
- * @param mixed $attr 要绑定的属性列表
+ * @param array $attr 要绑定的属性列表
* @return $this
*/
- public function bind($attr)
+ public function bind(array $attr)
{
- if (is_string($attr)) {
- $attr = explode(',', $attr);
- }
$this->bindAttr = $attr;
return $this;
@@ -231,7 +216,7 @@ abstract class OneToOne extends Relation
* @access public
* @return array
*/
- public function getBindAttr()
+ public function getBindAttr(): array
{
return $this->bindAttr;
}
@@ -244,12 +229,12 @@ abstract class OneToOne extends Relation
* @param Model $result 模型对象实例
* @return void
*/
- protected function match($model, $relation, &$result)
+ protected function match(string $model, string $relation, Model $result): void
{
// 重新组装模型数据
foreach ($result->getData() as $key => $val) {
if (strpos($key, '__')) {
- list($name, $attr) = explode('__', $key, 2);
+ [$name, $attr] = explode('__', $key, 2);
if ($name == $relation) {
$list[$name][$attr] = $val;
unset($result->$key);
@@ -265,67 +250,76 @@ abstract class OneToOne extends Relation
} else {
$relationModel = new $model($list[$relation]);
$relationModel->setParent(clone $result);
- $relationModel->isUpdate(true);
+ $relationModel->exists(true);
}
if (!empty($this->bindAttr)) {
- $this->bindAttr($relationModel, $result, $this->bindAttr);
+ $this->bindAttr($result, $relationModel);
}
} else {
$relationModel = null;
}
- $result->setRelation(Loader::parseName($relation), $relationModel);
+ $result->setRelation($relation, $relationModel);
}
/**
* 绑定关联属性到父模型
* @access protected
- * @param Model $model 关联模型对象
- * @param Model $result 父模型对象
+ * @param Model $result 父模型对象
+ * @param Model $model 关联模型对象
* @return void
* @throws Exception
*/
- protected function bindAttr($model, &$result)
+ protected function bindAttr(Model $result, Model $model = null): void
{
foreach ($this->bindAttr as $key => $attr) {
- $key = is_numeric($key) ? $attr : $key;
- if (isset($result->$key)) {
+ $key = is_numeric($key) ? $attr : $key;
+ $value = $result->getOrigin($key);
+
+ if (!is_null($value)) {
throw new Exception('bind attr has exists:' . $key);
- } else {
- $result->setAttr($key, $model ? $model->$attr : null);
}
+
+ $result->setAttr($key, $model ? $model->$attr : null);
}
}
/**
* 一对一 关联模型预查询(IN方式)
* @access public
- * @param array $where 关联预查询条件
- * @param string $key 关联键名
- * @param string $relation 关联名
- * @param string $subRelation 子关联
- * @param \Closure $closure
+ * @param array $where 关联预查询条件
+ * @param string $key 关联键名
+ * @param array $subRelation 子关联
+ * @param Closure $closure
+ * @param array $cache 关联缓存
* @return array
*/
- protected function eagerlyWhere($where, $key, $relation, $subRelation = '', $closure = null)
+ protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = [])
{
// 预载入关联查询 支持嵌套预载入
if ($closure) {
- $closure($this->query);
-
- if ($field = $this->query->getOptions('with_field')) {
- $this->query->field($field)->removeOption('with_field');
- }
+ $this->baseQuery = true;
+ $closure($this->getClosureType($closure));
}
- $list = $this->query->where($where)->with($subRelation)->select();
+ if ($this->withField) {
+ $this->query->field($this->withField);
+ }
+
+ $list = $this->query
+ ->where($where)
+ ->with($subRelation)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->select();
// 组装模型数据
$data = [];
foreach ($list as $set) {
- $data[$set->$key] = $set;
+ if (!isset($data[$set->$key])) {
+ $data[$set->$key] = $set;
+ }
}
return $data;
diff --git a/thinkphp/library/think/paginator/driver/Bootstrap.php b/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php
old mode 100755
new mode 100644
similarity index 86%
rename from thinkphp/library/think/paginator/driver/Bootstrap.php
rename to vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php
index ab5315c06..6d55c3944
--- a/thinkphp/library/think/paginator/driver/Bootstrap.php
+++ b/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php
@@ -2,7 +2,7 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
@@ -13,6 +13,9 @@ namespace think\paginator\driver;
use think\Paginator;
+/**
+ * Bootstrap 分页驱动
+ */
class Bootstrap extends Paginator
{
@@ -21,7 +24,7 @@ class Bootstrap extends Paginator
* @param string $text
* @return string
*/
- protected function getPreviousButton($text = "«")
+ protected function getPreviousButton(string $text = "«"): string
{
if ($this->currentPage() <= 1) {
@@ -40,7 +43,7 @@ class Bootstrap extends Paginator
* @param string $text
* @return string
*/
- protected function getNextButton($text = '»')
+ protected function getNextButton(string $text = '»'): string
{
if (!$this->hasMore) {
return $this->getDisabledTextWrapper($text);
@@ -55,7 +58,7 @@ class Bootstrap extends Paginator
* 页码按钮
* @return string
*/
- protected function getLinks()
+ protected function getLinks(): string
{
if ($this->simple) {
return '';
@@ -131,10 +134,10 @@ class Bootstrap extends Paginator
* 生成一个可点击的按钮
*
* @param string $url
- * @param int $page
+ * @param string $page
* @return string
*/
- protected function getAvailablePageWrapper($url, $page)
+ protected function getAvailablePageWrapper(string $url, string $page): string
{
return '' . $page . ' ';
}
@@ -145,7 +148,7 @@ class Bootstrap extends Paginator
* @param string $text
* @return string
*/
- protected function getDisabledTextWrapper($text)
+ protected function getDisabledTextWrapper(string $text): string
{
return '' . $text . ' ';
}
@@ -156,7 +159,7 @@ class Bootstrap extends Paginator
* @param string $text
* @return string
*/
- protected function getActivePageWrapper($text)
+ protected function getActivePageWrapper(string $text): string
{
return '' . $text . ' ';
}
@@ -166,7 +169,7 @@ class Bootstrap extends Paginator
*
* @return string
*/
- protected function getDots()
+ protected function getDots(): string
{
return $this->getDisabledTextWrapper('...');
}
@@ -177,7 +180,7 @@ class Bootstrap extends Paginator
* @param array $urls
* @return string
*/
- protected function getUrlLinks(array $urls)
+ protected function getUrlLinks(array $urls): string
{
$html = '';
@@ -192,10 +195,10 @@ class Bootstrap extends Paginator
* 生成普通页码按钮
*
* @param string $url
- * @param int $page
+ * @param string $page
* @return string
*/
- protected function getPageLinkWrapper($url, $page)
+ protected function getPageLinkWrapper(string $url, string $page): string
{
if ($this->currentPage() == $page) {
return $this->getActivePageWrapper($page);
diff --git a/vendor/topthink/think-orm/stubs/Exception.php b/vendor/topthink/think-orm/stubs/Exception.php
new file mode 100644
index 000000000..0fdba9c55
--- /dev/null
+++ b/vendor/topthink/think-orm/stubs/Exception.php
@@ -0,0 +1,59 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types=1);
+
+namespace think;
+
+/**
+ * 异常基础类
+ * @package think
+ */
+class Exception extends \Exception
+{
+ /**
+ * 保存异常页面显示的额外Debug数据
+ * @var array
+ */
+ protected $data = [];
+
+ /**
+ * 设置异常额外的Debug数据
+ * 数据将会显示为下面的格式
+ *
+ * Exception Data
+ * --------------------------------------------------
+ * Label 1
+ * key1 value1
+ * key2 value2
+ * Label 2
+ * key1 value1
+ * key2 value2
+ *
+ * @access protected
+ * @param string $label 数据分类,用于异常页面显示
+ * @param array $data 需要显示的数据,必须为关联数组
+ */
+ final protected function setData(string $label, array $data)
+ {
+ $this->data[$label] = $data;
+ }
+
+ /**
+ * 获取异常额外Debug数据
+ * 主要用于输出到异常页面便于调试
+ * @access public
+ * @return array 由setData设置的Debug数据
+ */
+ final public function getData()
+ {
+ return $this->data;
+ }
+}
diff --git a/vendor/topthink/think-orm/stubs/Facade.php b/vendor/topthink/think-orm/stubs/Facade.php
new file mode 100644
index 000000000..d801d8b0f
--- /dev/null
+++ b/vendor/topthink/think-orm/stubs/Facade.php
@@ -0,0 +1,65 @@
+
+// +----------------------------------------------------------------------
+declare(strict_types=1);
+
+namespace think;
+
+class Facade
+{
+ /**
+ * 始终创建新的对象实例
+ * @var bool
+ */
+ protected static $alwaysNewInstance;
+
+ protected static $instance;
+
+ /**
+ * 获取当前Facade对应类名
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {}
+
+ /**
+ * 创建Facade实例
+ * @static
+ * @access protected
+ * @param bool $newInstance 是否每次创建新的实例
+ * @return object
+ */
+ protected static function createFacade(bool $newInstance = false)
+ {
+ $class = static::getFacadeClass() ?: 'think\DbManager';
+
+ if (static::$alwaysNewInstance) {
+ $newInstance = true;
+ }
+
+ if ($newInstance) {
+ return new $class();
+ }
+
+ if (!self::$instance) {
+ self::$instance = new $class();
+ }
+
+ return self::$instance;
+
+ }
+
+ // 调用实际类的方法
+ public static function __callStatic($method, $params)
+ {
+ return call_user_func_array([static::createFacade(), $method], $params);
+ }
+}
diff --git a/vendor/topthink/think-orm/stubs/load_stubs.php b/vendor/topthink/think-orm/stubs/load_stubs.php
new file mode 100644
index 000000000..5854cda56
--- /dev/null
+++ b/vendor/topthink/think-orm/stubs/load_stubs.php
@@ -0,0 +1,9 @@
+ './template/',
+ 'cache_path' => './runtime/',
+ 'view_suffix' => 'html',
+];
+
+$template = new Template($config);
+// 模板变量赋值
+$template->assign(['name' => 'think']);
+// 读取模板文件渲染输出
+$template->fetch('index');
+// 完整模板文件渲染
+$template->fetch('./template/test.php');
+// 渲染内容输出
+$template->display($content);
+~~~
+
+支持静态调用
+
+~~~
+use think\facade\Template;
+
+Template::config([
+ 'view_path' => './template/',
+ 'cache_path' => './runtime/',
+ 'view_suffix' => 'html',
+]);
+Template::assign(['name' => 'think']);
+Template::fetch('index',['name' => 'think']);
+Template::display($content,['name' => 'think']);
+~~~
+
+详细用法参考[开发手册](https://www.kancloud.cn/manual/think-template/content)
\ No newline at end of file
diff --git a/vendor/topthink/think-template/composer.json b/vendor/topthink/think-template/composer.json
new file mode 100644
index 000000000..f4e1205c0
--- /dev/null
+++ b/vendor/topthink/think-template/composer.json
@@ -0,0 +1,20 @@
+{
+ "name": "topthink/think-template",
+ "description": "the php template engine",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "liu21st",
+ "email": "liu21st@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=7.1.0",
+ "psr/simple-cache": "^1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "think\\": "src"
+ }
+ }
+}
\ No newline at end of file
diff --git a/thinkphp/library/think/Template.php b/vendor/topthink/think-template/src/Template.php
old mode 100755
new mode 100644
similarity index 84%
rename from thinkphp/library/think/Template.php
rename to vendor/topthink/think-template/src/Template.php
index dc1acfa51..0feca8e20
--- a/thinkphp/library/think/Template.php
+++ b/vendor/topthink/think-template/src/Template.php
@@ -2,16 +2,18 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
+declare (strict_types = 1);
namespace think;
-use think\exception\TemplateNotFoundException;
+use Exception;
+use Psr\SimpleCache\CacheInterface;
/**
* ThinkPHP分离出来的模板引擎
@@ -20,7 +22,6 @@ use think\exception\TemplateNotFoundException;
*/
class Template
{
- protected $app;
/**
* 模板变量
* @var array
@@ -33,9 +34,9 @@ class Template
*/
protected $config = [
'view_path' => '', // 模板路径
- 'view_base' => '',
'view_suffix' => 'html', // 默认模板文件后缀
'view_depr' => DIRECTORY_SEPARATOR,
+ 'cache_path' => '',
'cache_suffix' => 'php', // 默认模板缓存后缀
'tpl_deny_func_list' => 'echo,exit', // 模板引擎禁用函数
'tpl_deny_php' => false, // 默认模板引擎是否禁用PHP原生代码
@@ -67,6 +68,12 @@ class Template
*/
private $literal = [];
+ /**
+ * 扩展解析规则
+ * @var array
+ */
+ private $extend = [];
+
/**
* 模板包含信息
* @var array
@@ -79,16 +86,20 @@ class Template
*/
protected $storage;
+ /**
+ * 查询缓存对象
+ * @var CacheInterface
+ */
+ protected $cache;
+
/**
* 架构函数
* @access public
* @param array $config
*/
- public function __construct(App $app, array $config = [])
+ public function __construct(array $config = [])
{
- $this->app = $app;
- $this->config['cache_path'] = $app->getRuntimePath() . 'temp/';
- $this->config = array_merge($this->config, $config);
+ $this->config = array_merge($this->config, $config);
$this->config['taglib_begin_origin'] = $this->config['taglib_begin'];
$this->config['taglib_end_origin'] = $this->config['taglib_end'];
@@ -99,37 +110,29 @@ class Template
$this->config['tpl_end'] = preg_quote($this->config['tpl_end'], '/');
// 初始化模板编译存储器
- $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File';
+ $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File';
+ $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type);
- $this->storage = Loader::factory($type, '\\think\\template\\driver\\', null);
- }
-
- public static function __make(Config $config)
- {
- return new static($config->pull('template'));
+ $this->storage = new $class();
}
/**
* 模板变量赋值
* @access public
- * @param mixed $name
- * @param mixed $value
- * @return void
+ * @param array $vars 模板变量
+ * @return $this
*/
- public function assign($name, $value = '')
+ public function assign(array $vars = [])
{
- if (is_array($name)) {
- $this->data = array_merge($this->data, $name);
- } else {
- $this->data[$name] = $value;
- }
+ $this->data = array_merge($this->data, $vars);
+ return $this;
}
/**
* 模板引擎参数赋值
* @access public
- * @param mixed $name
- * @param mixed $value
+ * @param string $name
+ * @param mixed $value
*/
public function __set($name, $value)
{
@@ -137,18 +140,37 @@ class Template
}
/**
- * 模板引擎配置项
+ * 设置缓存对象
* @access public
- * @param array|string $config
- * @return void|array
+ * @param CacheInterface $cache 缓存对象
+ * @return void
*/
- public function config($config)
+ public function setCache(CacheInterface $cache): void
{
- if (is_array($config)) {
- $this->config = array_merge($this->config, $config);
- } elseif (isset($this->config[$config])) {
- return $this->config[$config];
- }
+ $this->cache = $cache;
+ }
+
+ /**
+ * 模板引擎配置
+ * @access public
+ * @param array $config
+ * @return $this
+ */
+ public function config(array $config)
+ {
+ $this->config = array_merge($this->config, $config);
+ return $this;
+ }
+
+ /**
+ * 获取模板引擎配置项
+ * @access public
+ * @param string $name
+ * @return mixed
+ */
+ public function getConfig(string $name)
+ {
+ return $this->config[$name] ?? null;
}
/**
@@ -157,7 +179,7 @@ class Template
* @param string $name 变量名
* @return mixed
*/
- public function get($name = '')
+ public function get(string $name = '')
{
if ('' == $name) {
return $this->data;
@@ -178,31 +200,34 @@ class Template
}
/**
- * 渲染模板文件
+ * 扩展模板解析规则
* @access public
- * @param string $template 模板文件
- * @param array $vars 模板变量
- * @param array $config 模板参数
+ * @param string $rule 解析规则
+ * @param callable $callback 解析规则
* @return void
*/
- public function fetch($template, $vars = [], $config = [])
+ public function extend(string $rule, callable $callback = null): void
+ {
+ $this->extend[$rule] = $callback;
+ }
+
+ /**
+ * 渲染模板文件
+ * @access public
+ * @param string $template 模板文件
+ * @param array $vars 模板变量
+ * @return void
+ */
+ public function fetch(string $template, array $vars = []): void
{
if ($vars) {
- $this->data = $vars;
+ $this->data = array_merge($this->data, $vars);
}
- if ($config) {
- $this->config($config);
- }
-
- $cache = $this->app['cache'];
-
- if (!empty($this->config['cache_id']) && $this->config['display_cache']) {
+ if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) {
// 读取渲染缓存
- $cacheContent = $cache->get($this->config['cache_id']);
-
- if (false !== $cacheContent) {
- echo $cacheContent;
+ if ($this->cache->has($this->config['cache_id'])) {
+ echo $this->cache->get($this->config['cache_id']);
return;
}
}
@@ -220,7 +245,11 @@ class Template
// 页面缓存
ob_start();
- ob_implicit_flush(0);
+ if (PHP_VERSION > 8.0) {
+ ob_implicit_flush(false);
+ } else {
+ ob_implicit_flush(0);
+ }
// 读取编译存储
$this->storage->read($cacheFile, $this->data);
@@ -228,9 +257,9 @@ class Template
// 获取并清空缓存
$content = ob_get_clean();
- if (!empty($this->config['cache_id']) && $this->config['display_cache']) {
+ if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) {
// 缓存页面输出
- $cache->set($this->config['cache_id'], $content, $this->config['cache_time']);
+ $this->cache->set($this->config['cache_id'], $content, $this->config['cache_time']);
}
echo $content;
@@ -238,21 +267,32 @@ class Template
}
/**
- * 渲染模板内容
+ * 检查编译缓存是否存在
* @access public
- * @param string $content 模板内容
- * @param array $vars 模板变量
- * @param array $config 模板参数
- * @return void
+ * @param string $cacheId 缓存的id
+ * @return boolean
*/
- public function display($content, $vars = [], $config = [])
+ public function isCache(string $cacheId): bool
{
- if ($vars) {
- $this->data = $vars;
+ if ($cacheId && $this->cache && $this->config['display_cache']) {
+ // 缓存页面输出
+ return $this->cache->has($cacheId);
}
- if ($config) {
- $this->config($config);
+ return false;
+ }
+
+ /**
+ * 渲染模板内容
+ * @access public
+ * @param string $content 模板内容
+ * @param array $vars 模板变量
+ * @return void
+ */
+ public function display(string $content, array $vars = []): void
+ {
+ if ($vars) {
+ $this->data = array_merge($this->data, $vars);
}
$cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.');
@@ -269,11 +309,11 @@ class Template
/**
* 设置布局
* @access public
- * @param mixed $name 布局模板名称 false 则关闭布局
- * @param string $replace 布局模板内容替换标识
- * @return object
+ * @param mixed $name 布局模板名称 false 则关闭布局
+ * @param string $replace 布局模板内容替换标识
+ * @return $this
*/
- public function layout($name, $replace = '')
+ public function layout($name, string $replace = '')
{
if (false === $name) {
// 关闭布局
@@ -300,16 +340,22 @@ class Template
* 如果无效则需要重新编译
* @access private
* @param string $cacheFile 缓存文件名
- * @return boolean
+ * @return bool
*/
- private function checkCache($cacheFile)
+ private function checkCache(string $cacheFile): bool
{
if (!$this->config['tpl_cache'] || !is_file($cacheFile) || !$handle = @fopen($cacheFile, "r")) {
return false;
}
// 读取第一行
- preg_match('/\/\*(.+?)\*\//', fgets($handle), $matches);
+ $line = fgets($handle);
+
+ if (false === $line) {
+ return false;
+ }
+
+ preg_match('/\/\*(.+?)\*\//', $line, $matches);
if (!isset($matches[1])) {
return false;
@@ -333,30 +379,14 @@ class Template
return $this->storage->check($cacheFile, $this->config['cache_time']);
}
- /**
- * 检查编译缓存是否存在
- * @access public
- * @param string $cacheId 缓存的id
- * @return boolean
- */
- public function isCache($cacheId)
- {
- if ($cacheId && $this->config['display_cache']) {
- // 缓存页面输出
- return $this->app['cache']->has($cacheId);
- }
-
- return false;
- }
-
/**
* 编译模板文件内容
* @access private
- * @param string $content 模板内容
- * @param string $cacheFile 缓存文件名
+ * @param string $content 模板内容
+ * @param string $cacheFile 缓存文件名
* @return void
*/
- private function compiler(&$content, $cacheFile)
+ private function compiler(string &$content, string $cacheFile): void
{
// 判断是否启用布局
if ($this->config['layout_on']) {
@@ -408,7 +438,7 @@ class Template
* @param string $content 要解析的模板内容
* @return void
*/
- public function parse(&$content)
+ public function parse(string &$content): void
{
// 内容为空不解析
if (empty($content)) {
@@ -477,9 +507,9 @@ class Template
* @access private
* @param string $content 要解析的模板内容
* @return void
- * @throws \think\Exception
+ * @throws Exception
*/
- private function parsePhp(&$content)
+ private function parsePhp(string &$content): void
{
// 短标签的情况要将' . "\n", $content);
@@ -496,7 +526,7 @@ class Template
* @param string $content 要解析的模板内容
* @return void
*/
- private function parseLayout(&$content)
+ private function parseLayout(string &$content): void
{
// 读取模板中的布局标签
if (preg_match($this->getRegex('layout'), $content, $matches)) {
@@ -526,7 +556,7 @@ class Template
* @param string $content 要解析的模板内容
* @return void
*/
- private function parseInclude(&$content)
+ private function parseInclude(string &$content): void
{
$regex = $this->getRegex('include');
$func = function ($template) use (&$func, &$regex, &$content) {
@@ -566,7 +596,7 @@ class Template
* @param string $content 要解析的模板内容
* @return void
*/
- private function parseExtend(&$content)
+ private function parseExtend(string &$content): void
{
$regex = $this->getRegex('extend');
$array = $blocks = $baseBlocks = [];
@@ -655,7 +685,7 @@ class Template
* @param boolean $restore 是否为还原
* @return void
*/
- private function parseLiteral(&$content, $restore = false)
+ private function parseLiteral(string &$content, bool $restore = false): void
{
$regex = $this->getRegex($restore ? 'restoreliteral' : 'literal');
@@ -690,7 +720,7 @@ class Template
* @param boolean $sort 是否排序
* @return array
*/
- private function parseBlock(&$content, $sort = false)
+ private function parseBlock(string &$content, bool $sort = false): array
{
$regex = $this->getRegex('block');
$result = [];
@@ -742,7 +772,7 @@ class Template
* @param string $content 模板内容
* @return array|null
*/
- private function getIncludeTagLib(&$content)
+ private function getIncludeTagLib(string &$content)
{
// 搜索是否有TagLib标签
if (preg_match($this->getRegex('taglib'), $content, $matches)) {
@@ -761,7 +791,7 @@ class Template
* @param boolean $hide 是否隐藏标签库前缀
* @return void
*/
- public function parseTagLib($tagLib, &$content, $hide = false)
+ public function parseTagLib(string $tagLib, string &$content, bool $hide = false): void
{
if (false !== strpos($tagLib, '\\')) {
// 支持指定标签库的命名空间
@@ -783,7 +813,7 @@ class Template
* @param string $name 不为空时返回指定的属性名
* @return array
*/
- public function parseAttr($str, $name = null)
+ public function parseAttr(string $str, string $name = null): array
{
$regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is';
$array = [];
@@ -809,7 +839,7 @@ class Template
* @param string $content 要解析的模板内容
* @return void
*/
- private function parseTag(&$content)
+ private function parseTag(string &$content): void
{
$regex = $this->getRegex('tag');
@@ -945,7 +975,7 @@ class Template
* @param string $varStr 变量数据
* @return void
*/
- public function parseVar(&$varStr)
+ public function parseVar(string &$varStr): void
{
$varStr = trim($varStr);
@@ -963,22 +993,15 @@ class Template
$vars = explode('.', $match[0]);
$first = array_shift($vars);
- if ('$Think' == $first) {
+ if (isset($this->extend[$first])) {
+ $callback = $this->extend[$first];
+ $parseStr = $callback($vars);
+ } elseif ('$Request' == $first) {
+ // 输出请求变量
+ $parseStr = $this->parseRequestVar($vars);
+ } elseif ('$Think' == $first) {
// 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出
$parseStr = $this->parseThinkVar($vars);
- } elseif ('$Request' == $first) {
- // 获取Request请求对象参数
- $method = array_shift($vars);
- if (!empty($vars)) {
- $params = implode('.', $vars);
- if ('true' != $params) {
- $params = '\'' . $params . '\'';
- }
- } else {
- $params = '';
- }
-
- $parseStr = 'app(\'request\')->' . $method . '(' . $params . ')';
} else {
switch ($this->config['tpl_var_identify']) {
case 'array': // 识别为数组
@@ -1008,11 +1031,11 @@ class Template
* 对模板中使用了函数的变量进行解析
* 格式 {$varname|function1|function2=arg1,arg2}
* @access public
- * @param string $varStr 变量字符串
- * @param bool $autoescape 自动转义
- * @return void
+ * @param string $varStr 变量字符串
+ * @param bool $autoescape 自动转义
+ * @return string
*/
- public function parseVarFunction(&$varStr, $autoescape = true)
+ public function parseVarFunction(string &$varStr, bool $autoescape = true): string
{
if (!$autoescape && false === strpos($varStr, '|')) {
return $varStr;
@@ -1098,6 +1121,47 @@ class Template
return $varStr;
}
+ /**
+ * 请求变量解析
+ * 格式 以 $Request. 打头的变量属于请求变量
+ * @access public
+ * @param array $vars 变量数组
+ * @return string
+ */
+ public function parseRequestVar(array $vars): string
+ {
+ $type = strtoupper(trim(array_shift($vars)));
+ $param = implode('.', $vars);
+
+ switch ($type) {
+ case 'SERVER':
+ $parseStr = '$_SERVER[\'' . $param . '\']';
+ break;
+ case 'GET':
+ $parseStr = '$_GET[\'' . $param . '\']';
+ break;
+ case 'POST':
+ $parseStr = '$_POST[\'' . $param . '\']';
+ break;
+ case 'COOKIE':
+ $parseStr = '$_COOKIE[\'' . $param . '\']';
+ break;
+ case 'SESSION':
+ $parseStr = '$_SESSION[\'' . $param . '\']';
+ break;
+ case 'ENV':
+ $parseStr = '$_ENV[\'' . $param . '\']';
+ break;
+ case 'REQUEST':
+ $parseStr = '$_REQUEST[\'' . $param . '\']';
+ break;
+ default:
+ $parseStr = '\'\'';
+ }
+
+ return $parseStr;
+ }
+
/**
* 特殊模板变量解析
* 格式 以 $Think. 打头的变量属于特殊模板变量
@@ -1105,68 +1169,26 @@ class Template
* @param array $vars 变量数组
* @return string
*/
- public function parseThinkVar($vars)
+ public function parseThinkVar(array $vars): string
{
$type = strtoupper(trim(array_shift($vars)));
$param = implode('.', $vars);
- if ($vars) {
- switch ($type) {
- case 'SERVER':
- $parseStr = 'app(\'request\')->server(\'' . $param . '\')';
- break;
- case 'GET':
- $parseStr = 'app(\'request\')->get(\'' . $param . '\')';
- break;
- case 'POST':
- $parseStr = 'app(\'request\')->post(\'' . $param . '\')';
- break;
- case 'COOKIE':
- $parseStr = 'app(\'cookie\')->get(\'' . $param . '\')';
- break;
- case 'SESSION':
- $parseStr = 'app(\'session\')->get(\'' . $param . '\')';
- break;
- case 'ENV':
- $parseStr = 'app(\'request\')->env(\'' . $param . '\')';
- break;
- case 'REQUEST':
- $parseStr = 'app(\'request\')->request(\'' . $param . '\')';
- break;
- case 'CONST':
- $parseStr = strtoupper($param);
- break;
- case 'LANG':
- $parseStr = 'app(\'lang\')->get(\'' . $param . '\')';
- break;
- case 'CONFIG':
- $parseStr = 'app(\'config\')->get(\'' . $param . '\')';
- break;
- default:
- $parseStr = '\'\'';
- break;
- }
- } else {
- switch ($type) {
- case 'NOW':
- $parseStr = "date('Y-m-d g:i a',time())";
- break;
- case 'VERSION':
- $parseStr = 'app()->version()';
- break;
- case 'LDELIM':
- $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\'';
- break;
- case 'RDELIM':
- $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\'';
- break;
- default:
- if (defined($type)) {
- $parseStr = $type;
- } else {
- $parseStr = '';
- }
- }
+ switch ($type) {
+ case 'CONST':
+ $parseStr = strtoupper($param);
+ break;
+ case 'NOW':
+ $parseStr = "date('Y-m-d g:i a',time())";
+ break;
+ case 'LDELIM':
+ $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\'';
+ break;
+ case 'RDELIM':
+ $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\'';
+ break;
+ default:
+ $parseStr = defined($type) ? $type : '\'\'';
}
return $parseStr;
@@ -1178,7 +1200,7 @@ class Template
* @param string $templateName 模板文件名
* @return string
*/
- private function parseTemplateName($templateName)
+ private function parseTemplateName(string $templateName): string
{
$array = explode(',', $templateName);
$parseStr = '';
@@ -1208,14 +1230,11 @@ class Template
* 解析模板文件名
* @access private
* @param string $template 文件名
- * @return string|false
+ * @return string
*/
- private function parseTemplateFile($template)
+ private function parseTemplateFile(string $template): string
{
if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
- if (strpos($template, '@')) {
- list($module, $template) = explode('@', $template);
- }
if (0 !== strpos($template, '/')) {
$template = str_replace(['/', ':'], $this->config['view_depr'], $template);
@@ -1223,14 +1242,7 @@ class Template
$template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1));
}
- if ($this->config['view_base']) {
- $module = isset($module) ? $module : $this->app['request']->module();
- $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : '');
- } else {
- $path = isset($module) ? $this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . basename($this->config['view_path']) . DIRECTORY_SEPARATOR : $this->config['view_path'];
- }
-
- $template = $path . $template . '.' . ltrim($this->config['view_suffix'], '.');
+ $template = $this->config['view_path'] . $template . '.' . ltrim($this->config['view_suffix'], '.');
}
if (is_file($template)) {
@@ -1240,7 +1252,7 @@ class Template
return $template;
}
- throw new TemplateNotFoundException('template not exists:' . $template, $template);
+ throw new Exception('template not exists:' . $template);
}
/**
@@ -1249,7 +1261,7 @@ class Template
* @param string $tagName 标签名
* @return string
*/
- private function getRegex($tagName)
+ private function getRegex(string $tagName): string
{
$regex = '';
if ('tag' == $tagName) {
@@ -1311,7 +1323,7 @@ class Template
public function __debugInfo()
{
$data = get_object_vars($this);
- unset($data['app'], $data['storege']);
+ unset($data['storage']);
return $data;
}
diff --git a/vendor/topthink/think-template/src/facade/Template.php b/vendor/topthink/think-template/src/facade/Template.php
new file mode 100644
index 000000000..665a180a8
--- /dev/null
+++ b/vendor/topthink/think-template/src/facade/Template.php
@@ -0,0 +1,83 @@
+
+// +----------------------------------------------------------------------
+
+namespace think\facade;
+
+if (class_exists('think\Facade')) {
+ class Facade extends \think\Facade
+ {}
+} else {
+ class Facade
+ {
+ /**
+ * 始终创建新的对象实例
+ * @var bool
+ */
+ protected static $alwaysNewInstance;
+
+ protected static $instance;
+
+ /**
+ * 获取当前Facade对应类名
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {}
+
+ /**
+ * 创建Facade实例
+ * @static
+ * @access protected
+ * @return object
+ */
+ protected static function createFacade()
+ {
+ $class = static::getFacadeClass() ?: 'think\Template';
+
+ if (static::$alwaysNewInstance) {
+ return new $class();
+ }
+
+ if (!self::$instance) {
+ self::$instance = new $class();
+ }
+
+ return self::$instance;
+
+ }
+
+ // 调用实际类的方法
+ public static function __callStatic($method, $params)
+ {
+ return call_user_func_array([static::createFacade(), $method], $params);
+ }
+ }
+}
+
+/**
+ * @see \think\Template
+ * @mixin \think\Template
+ */
+class Template extends Facade
+{
+ protected static $alwaysNewInstance = true;
+
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'think\Template';
+ }
+}
diff --git a/thinkphp/library/think/template/TagLib.php b/vendor/topthink/think-template/src/template/TagLib.php
old mode 100755
new mode 100644
similarity index 91%
rename from thinkphp/library/think/template/TagLib.php
rename to vendor/topthink/think-template/src/template/TagLib.php
index bbbb2c035..f6c8fbb84
--- a/thinkphp/library/think/template/TagLib.php
+++ b/vendor/topthink/think-template/src/template/TagLib.php
@@ -2,7 +2,7 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
@@ -11,7 +11,8 @@
namespace think\template;
-use think\Exception;
+use Exception;
+use think\Template;
/**
* ThinkPHP标签库TagLib解析基类
@@ -70,9 +71,9 @@ class TagLib
/**
* 架构函数
* @access public
- * @param \stdClass $template 模板引擎对象
+ * @param Template $template 模板引擎对象
*/
- public function __construct($template)
+ public function __construct(Template $template)
{
$this->tpl = $template;
}
@@ -84,7 +85,7 @@ class TagLib
* @param string $lib 标签库名
* @return void
*/
- public function parseTag(&$content, $lib = '')
+ public function parseTag(string &$content, string $lib = ''): void
{
$tags = [];
$lib = $lib ? strtolower($lib) . ':' : '';
@@ -185,8 +186,6 @@ class TagLib
return $this->$method($attrs, '');
}, $content);
}
-
- return;
}
/**
@@ -196,10 +195,10 @@ class TagLib
* @param boolean $close 是否为闭合标签
* @return string
*/
- public function getRegex($tags, $close)
+ public function getRegex($tags, bool $close): string
{
- $begin = $this->tpl->config('taglib_begin');
- $end = $this->tpl->config('taglib_end');
+ $begin = $this->tpl->getConfig('taglib_begin');
+ $end = $this->tpl->getConfig('taglib_end');
$single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false;
$tagName = is_array($tags) ? implode('|', $tags) : $tags;
@@ -230,7 +229,7 @@ class TagLib
* @param string $alias 别名
* @return array
*/
- public function parseAttr($str, $name, $alias = '')
+ public function parseAttr(string $str, string $name, string $alias = ''): array
{
$regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is';
$result = [];
@@ -275,8 +274,8 @@ class TagLib
if (!empty($this->tags[$name]['expression'])) {
static $_taglibs;
if (!isset($_taglibs[$name])) {
- $_taglibs[$name][0] = strlen($this->tpl->config('taglib_begin_origin') . $name);
- $_taglibs[$name][1] = strlen($this->tpl->config('taglib_end_origin'));
+ $_taglibs[$name][0] = strlen($this->tpl->getConfig('taglib_begin_origin') . $name);
+ $_taglibs[$name][1] = strlen($this->tpl->getConfig('taglib_end_origin'));
}
$result['expression'] = substr($str, $_taglibs[$name][0], -$_taglibs[$name][1]);
// 清除自闭合标签尾部/
@@ -296,16 +295,15 @@ class TagLib
* @param string $condition 表达式标签内容
* @return string
*/
- public function parseCondition($condition)
+ public function parseCondition(string $condition): string
{
- if (!strpos($condition, '::') && strpos($condition, ':')) {
+ if (strpos($condition, ':')) {
$condition = ' ' . substr(strstr($condition, ':'), 1);
}
$condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition);
$this->tpl->parseVar($condition);
- // $this->tpl->parseVarFunction($condition); // XXX: 此句能解析表达式中用|分隔的函数,但表达式中如果有|、||这样的逻辑运算就产生了歧异
return $condition;
}
@@ -315,7 +313,7 @@ class TagLib
* @param string $name 变量描述
* @return string
*/
- public function autoBuildVar(&$name)
+ public function autoBuildVar(string &$name): string
{
$flag = substr($name, 0, 1);
@@ -344,7 +342,7 @@ class TagLib
* @access public
* @return array
*/
- public function getTags()
+ public function getTags(): array
{
return $this->tags;
}
diff --git a/thinkphp/library/think/template/driver/File.php b/vendor/topthink/think-template/src/template/driver/File.php
old mode 100755
new mode 100644
similarity index 86%
rename from thinkphp/library/think/template/driver/File.php
rename to vendor/topthink/think-template/src/template/driver/File.php
index 3b96a0f3b..510d10a0b
--- a/thinkphp/library/think/template/driver/File.php
+++ b/vendor/topthink/think-template/src/template/driver/File.php
@@ -2,7 +2,7 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
@@ -11,7 +11,7 @@
namespace think\template\driver;
-use think\Exception;
+use Exception;
class File
{
@@ -22,9 +22,9 @@ class File
* @access public
* @param string $cacheFile 缓存的文件名
* @param string $content 缓存的内容
- * @return void|array
+ * @return void
*/
- public function write($cacheFile, $content)
+ public function write(string $cacheFile, string $content): void
{
// 检测模板目录
$dir = dirname($cacheFile);
@@ -46,7 +46,7 @@ class File
* @param array $vars 变量数组
* @return void
*/
- public function read($cacheFile, $vars = [])
+ public function read(string $cacheFile, array $vars = []): void
{
$this->cacheFile = $cacheFile;
@@ -64,9 +64,9 @@ class File
* @access public
* @param string $cacheFile 缓存的文件名
* @param int $cacheTime 缓存时间
- * @return boolean
+ * @return bool
*/
- public function check($cacheFile, $cacheTime)
+ public function check(string $cacheFile, int $cacheTime): bool
{
// 缓存文件不存在, 直接返回false
if (!file_exists($cacheFile)) {
diff --git a/thinkphp/library/think/exception/TemplateNotFoundException.php b/vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php
old mode 100755
new mode 100644
similarity index 75%
rename from thinkphp/library/think/exception/TemplateNotFoundException.php
rename to vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php
index 420206931..dd88b327d
--- a/thinkphp/library/think/exception/TemplateNotFoundException.php
+++ b/vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php
@@ -2,20 +2,20 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006-2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
-// | Author: yunwuxin <448901948@qq.com>
+// | Author: liu21st
// +----------------------------------------------------------------------
-namespace think\exception;
+namespace think\template\exception;
class TemplateNotFoundException extends \RuntimeException
{
protected $template;
- public function __construct($message, $template = '')
+ public function __construct(string $message, string $template = '')
{
$this->message = $message;
$this->template = $template;
@@ -26,7 +26,7 @@ class TemplateNotFoundException extends \RuntimeException
* @access public
* @return string
*/
- public function getTemplate()
+ public function getTemplate(): string
{
return $this->template;
}
diff --git a/thinkphp/library/think/template/taglib/Cx.php b/vendor/topthink/think-template/src/template/taglib/Cx.php
old mode 100755
new mode 100644
similarity index 93%
rename from thinkphp/library/think/template/taglib/Cx.php
rename to vendor/topthink/think-template/src/template/taglib/Cx.php
index ad741f289..bccafc1be
--- a/thinkphp/library/think/template/taglib/Cx.php
+++ b/vendor/topthink/think-template/src/template/taglib/Cx.php
@@ -2,7 +2,7 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
-// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// | Copyright (c) 2006-2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
@@ -60,7 +60,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagPhp($tag, $content)
+ public function tagPhp(array $tag, string $content): string
{
$parseStr = '';
return $parseStr;
@@ -76,9 +76,9 @@ class Cx extends Taglib
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
- * @return string|void
+ * @return string
*/
- public function tagVolist($tag, $content)
+ public function tagVolist(array $tag, string $content): string
{
$name = $tag['name'];
$id = $tag['id'];
@@ -116,11 +116,7 @@ class Cx extends Taglib
$parseStr .= $content;
$parseStr .= '';
- if (!empty($parseStr)) {
- return $parseStr;
- }
-
- return;
+ return $parseStr;
}
/**
@@ -132,9 +128,9 @@ class Cx extends Taglib
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
- * @return string|void
+ * @return string
*/
- public function tagForeach($tag, $content)
+ public function tagForeach(array $tag, string $content): string
{
// 直接使用表达式
if (!empty($tag['expression'])) {
@@ -203,11 +199,7 @@ class Cx extends Taglib
$parseStr .= $content;
$parseStr .= '';
- if (!empty($parseStr)) {
- return $parseStr;
- }
-
- return;
+ return $parseStr;
}
/**
@@ -223,7 +215,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagIf($tag, $content)
+ public function tagIf(array $tag, string $content): string
{
$condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition'];
$condition = $this->parseCondition($condition);
@@ -240,7 +232,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagElseif($tag, $content)
+ public function tagElseif(array $tag, string $content): string
{
$condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition'];
$condition = $this->parseCondition($condition);
@@ -256,7 +248,7 @@ class Cx extends Taglib
* @param array $tag 标签属性
* @return string
*/
- public function tagElse($tag)
+ public function tagElse(array $tag): string
{
$parseStr = '';
@@ -276,7 +268,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagSwitch($tag, $content)
+ public function tagSwitch(array $tag, string $content): string
{
$name = !empty($tag['expression']) ? $tag['expression'] : $tag['name'];
$name = $this->autoBuildVar($name);
@@ -292,7 +284,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagCase($tag, $content)
+ public function tagCase(array $tag, string $content): string
{
$value = isset($tag['expression']) ? $tag['expression'] : $tag['value'];
$flag = substr($value, 0, 1);
@@ -325,10 +317,9 @@ class Cx extends Taglib
* 使用: {default /}ddfdf
* @access public
* @param array $tag 标签属性
- * @param string $content 标签内容
* @return string
*/
- public function tagDefault($tag)
+ public function tagDefault(array $tag): string
{
$parseStr = '';
@@ -344,7 +335,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagCompare($tag, $content)
+ public function tagCompare(array $tag, string $content): string
{
$name = $tag['name'];
$value = $tag['value'];
@@ -382,7 +373,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagRange($tag, $content)
+ public function tagRange(array $tag, string $content): string
{
$name = $tag['name'];
$value = $tag['value'];
@@ -420,7 +411,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagPresent($tag, $content)
+ public function tagPresent(array $tag, string $content): string
{
$name = $tag['name'];
$name = $this->autoBuildVar($name);
@@ -438,7 +429,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagNotpresent($tag, $content)
+ public function tagNotpresent(array $tag, string $content): string
{
$name = $tag['name'];
$name = $this->autoBuildVar($name);
@@ -456,7 +447,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagEmpty($tag, $content)
+ public function tagEmpty(array $tag, string $content): string
{
$name = $tag['name'];
$name = $this->autoBuildVar($name);
@@ -474,7 +465,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagNotempty($tag, $content)
+ public function tagNotempty(array $tag, string $content): string
{
$name = $tag['name'];
$name = $this->autoBuildVar($name);
@@ -491,7 +482,7 @@ class Cx extends Taglib
* @param string $content
* @return string
*/
- public function tagDefined($tag, $content)
+ public function tagDefined(array $tag, string $content): string
{
$name = $tag['name'];
$parseStr = '' . $content . '';
@@ -507,7 +498,7 @@ class Cx extends Taglib
* @param string $content
* @return string
*/
- public function tagNotdefined($tag, $content)
+ public function tagNotdefined(array $tag, string $content): string
{
$name = $tag['name'];
$parseStr = '' . $content . '';
@@ -523,7 +514,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagLoad($tag, $content)
+ public function tagLoad(array $tag, string $content): string
{
$file = isset($tag['file']) ? $tag['file'] : $tag['href'];
$type = isset($tag['type']) ? strtolower($tag['type']) : '';
@@ -570,7 +561,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagAssign($tag, $content)
+ public function tagAssign(array $tag, string $content): string
{
$name = $this->autoBuildVar($tag['name']);
$flag = substr($tag['value'], 0, 1);
@@ -595,7 +586,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagDefine($tag, $content)
+ public function tagDefine(array $tag, string $content): string
{
$name = '\'' . $tag['name'] . '\'';
$flag = substr($tag['value'], 0, 1);
@@ -622,7 +613,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagFor($tag, $content)
+ public function tagFor(array $tag, string $content): string
{
//设置默认值
$start = 0;
@@ -675,7 +666,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagUrl($tag, $content)
+ public function tagUrl(array $tag, string $content): string
{
$url = isset($tag['link']) ? $tag['link'] : '';
$vars = isset($tag['vars']) ? $tag['vars'] : '';
@@ -702,7 +693,7 @@ class Cx extends Taglib
* @param string $content 标签内容
* @return string
*/
- public function tagFunction($tag, $content)
+ public function tagFunction(array $tag, string $content): string
{
$name = !empty($tag['name']) ? $tag['name'] : 'func';
$vars = !empty($tag['vars']) ? $tag['vars'] : '';
diff --git a/vendor/topthink/think-trace/.gitignore b/vendor/topthink/think-trace/.gitignore
new file mode 100644
index 000000000..485dee64b
--- /dev/null
+++ b/vendor/topthink/think-trace/.gitignore
@@ -0,0 +1 @@
+.idea
diff --git a/vendor/topthink/think-trace/LICENSE b/vendor/topthink/think-trace/LICENSE
new file mode 100644
index 000000000..261eeb9e9
--- /dev/null
+++ b/vendor/topthink/think-trace/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/topthink/think-trace/README.md b/vendor/topthink/think-trace/README.md
new file mode 100644
index 000000000..6ed63ec42
--- /dev/null
+++ b/vendor/topthink/think-trace/README.md
@@ -0,0 +1,15 @@
+# think-trace
+
+用于ThinkPHP6+的页面Trace扩展,支持Html页面和浏览器控制台两种方式输出。
+
+## 安装
+
+~~~
+composer require topthink/think-trace
+~~~
+
+## 配置
+
+安装后config目录下会自带trace.php配置文件。
+
+type参数用于指定trace类型,支持html和console两种方式。
\ No newline at end of file
diff --git a/vendor/topthink/think-trace/composer.json b/vendor/topthink/think-trace/composer.json
new file mode 100644
index 000000000..5af58545e
--- /dev/null
+++ b/vendor/topthink/think-trace/composer.json
@@ -0,0 +1,31 @@
+{
+ "name": "topthink/think-trace",
+ "description": "thinkphp debug trace",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "liu21st",
+ "email": "liu21st@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=7.1.0",
+ "topthink/framework": "^6.0.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "think\\trace\\": "src"
+ }
+ },
+ "extra": {
+ "think":{
+ "services":[
+ "think\\trace\\Service"
+ ],
+ "config":{
+ "trace": "src/config.php"
+ }
+ }
+ },
+ "minimum-stability": "dev"
+}
diff --git a/thinkphp/library/think/debug/Console.php b/vendor/topthink/think-trace/src/Console.php
old mode 100755
new mode 100644
similarity index 66%
rename from thinkphp/library/think/debug/Console.php
rename to vendor/topthink/think-trace/src/Console.php
index 5cbaa0f2f..179d4ea2b
--- a/thinkphp/library/think/debug/Console.php
+++ b/vendor/topthink/think-trace/src/Console.php
@@ -1,156 +1,173 @@
-
-// +----------------------------------------------------------------------
-
-namespace think\debug;
-
-use think\Container;
-use think\Db;
-use think\Response;
-
-/**
- * 浏览器调试输出
- */
-class Console
-{
- protected $config = [
- 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'],
- ];
-
- // 实例化并传入参数
- public function __construct($config = [])
- {
- if (is_array($config)) {
- $this->config = array_merge($this->config, $config);
- }
- }
-
- /**
- * 调试输出接口
- * @access public
- * @param Response $response Response对象
- * @param array $log 日志信息
- * @return bool
- */
- public function output(Response $response, array $log = [])
- {
- $request = Container::get('request');
- $contentType = $response->getHeader('Content-Type');
- $accept = $request->header('accept');
- if (strpos($accept, 'application/json') === 0 || $request->isAjax()) {
- return false;
- } elseif (!empty($contentType) && strpos($contentType, 'html') === false) {
- return false;
- }
- // 获取基本信息
- $runtime = number_format(microtime(true) - Container::get('app')->getBeginTime(), 10);
- $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞';
- $mem = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2);
-
- if ($request->host()) {
- $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true);
- } else {
- $uri = 'cmd:' . implode(' ', $_SERVER['argv']);
- }
-
- // 页面Trace信息
- $base = [
- '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri,
- '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()),
- '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ',
- '缓存信息' => Container::get('cache')->getReadTimes() . ' reads,' . Container::get('cache')->getWriteTimes() . ' writes',
- ];
-
- if (session_id()) {
- $base['会话信息'] = 'SESSION_ID=' . session_id();
- }
-
- $info = Container::get('debug')->getFile(true);
-
- // 页面Trace信息
- $trace = [];
- foreach ($this->config['tabs'] as $name => $title) {
- $name = strtolower($name);
- switch ($name) {
- case 'base': // 基本信息
- $trace[$title] = $base;
- break;
- case 'file': // 文件信息
- $trace[$title] = $info;
- break;
- default: // 调试信息
- if (strpos($name, '|')) {
- // 多组信息
- $names = explode('|', $name);
- $result = [];
- foreach ($names as $name) {
- $result = array_merge($result, isset($log[$name]) ? $log[$name] : []);
- }
- $trace[$title] = $result;
- } else {
- $trace[$title] = isset($log[$name]) ? $log[$name] : '';
- }
- }
- }
-
- //输出到控制台
- $lines = '';
- foreach ($trace as $type => $msg) {
- $lines .= $this->console($type, $msg);
- }
- $js = <<
-{$lines}
-
-JS;
- return $js;
- }
-
- protected function console($type, $msg)
- {
- $type = strtolower($type);
- $trace_tabs = array_values($this->config['tabs']);
- $line[] = ($type == $trace_tabs[0] || '调试' == $type || '错误' == $type)
- ? "console.group('{$type}');"
- : "console.groupCollapsed('{$type}');";
-
- foreach ((array) $msg as $key => $m) {
- switch ($type) {
- case '调试':
- $var_type = gettype($m);
- if (in_array($var_type, ['array', 'string'])) {
- $line[] = "console.log(" . json_encode($m) . ");";
- } else {
- $line[] = "console.log(" . json_encode(var_export($m, 1)) . ");";
- }
- break;
- case '错误':
- $msg = str_replace("\n", '\n', addslashes(is_scalar($m) ? $m : json_encode($m)));
- $style = 'color:#F4006B;font-size:14px;';
- $line[] = "console.error(\"%c{$msg}\", \"{$style}\");";
- break;
- case 'sql':
- $msg = str_replace("\n", '\n', addslashes($m));
- $style = "color:#009bb4;";
- $line[] = "console.log(\"%c{$msg}\", \"{$style}\");";
- break;
- default:
- $m = is_string($key) ? $key . ' ' . $m : $key + 1 . ' ' . $m;
- $msg = json_encode($m);
- $line[] = "console.log({$msg});";
- break;
- }
- }
- $line[] = "console.groupEnd();";
- return implode(PHP_EOL, $line);
- }
-
-}
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+namespace think\trace;
+
+use think\App;
+use think\Response;
+
+/**
+ * 浏览器调试输出
+ */
+class Console
+{
+ protected $config = [
+ 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'],
+ ];
+
+ // 实例化并传入参数
+ public function __construct(array $config = [])
+ {
+ $this->config = array_merge($this->config, $config);
+ }
+
+ /**
+ * 调试输出接口
+ * @access public
+ * @param Response $response Response对象
+ * @param array $log 日志信息
+ * @return string|bool
+ */
+ public function output(App $app, Response $response, array $log = [])
+ {
+ $request = $app->request;
+ $contentType = $response->getHeader('Content-Type');
+
+ if ($request->isJson() || $request->isAjax()) {
+ return false;
+ } elseif (!empty($contentType) && strpos($contentType, 'html') === false) {
+ return false;
+ } elseif ($response->getCode() == 204) {
+ return false;
+ }
+
+ // 获取基本信息
+ $runtime = number_format(microtime(true) - $app->getBeginTime(), 10, '.', '');
+ $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞';
+ $mem = number_format((memory_get_usage() - $app->getBeginMem()) / 1024, 2);
+
+ if ($request->host()) {
+ $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true);
+ } else {
+ $uri = 'cmd:' . implode(' ', $_SERVER['argv']);
+ }
+
+ // 页面Trace信息
+ $base = [
+ '请求信息' => date('Y-m-d H:i:s', $request->time() ?: time()) . ' ' . $uri,
+ '运行时间' => number_format((float) $runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()),
+ '查询信息' => $app->db->getQueryTimes() . ' queries',
+ '缓存信息' => $app->cache->getReadTimes() . ' reads,' . $app->cache->getWriteTimes() . ' writes',
+ ];
+
+ if (isset($app->session)) {
+ $base['会话信息'] = 'SESSION_ID=' . $app->session->getId();
+ }
+
+ $info = $this->getFileInfo();
+
+ // 页面Trace信息
+ $trace = [];
+ foreach ($this->config['tabs'] as $name => $title) {
+ $name = strtolower($name);
+ switch ($name) {
+ case 'base': // 基本信息
+ $trace[$title] = $base;
+ break;
+ case 'file': // 文件信息
+ $trace[$title] = $info;
+ break;
+ default: // 调试信息
+ if (strpos($name, '|')) {
+ // 多组信息
+ $names = explode('|', $name);
+ $result = [];
+ foreach ($names as $item) {
+ $result = array_merge($result, $log[$item] ?? []);
+ }
+ $trace[$title] = $result;
+ } else {
+ $trace[$title] = $log[$name] ?? '';
+ }
+ }
+ }
+
+ //输出到控制台
+ $lines = '';
+ foreach ($trace as $type => $msg) {
+ $lines .= $this->console($type, empty($msg) ? [] : $msg);
+ }
+ $js = <<
+{$lines}
+
+JS;
+ return $js;
+ }
+
+ protected function console(string $type, $msg)
+ {
+ $type = strtolower($type);
+ $trace_tabs = array_values($this->config['tabs']);
+ $line = [];
+ $line[] = ($type == $trace_tabs[0] || '调试' == $type || '错误' == $type)
+ ? "console.group('{$type}');"
+ : "console.groupCollapsed('{$type}');";
+
+ foreach ((array) $msg as $key => $m) {
+ switch ($type) {
+ case '调试':
+ $var_type = gettype($m);
+ if (in_array($var_type, ['array', 'string'])) {
+ $line[] = "console.log(" . json_encode($m) . ");";
+ } else {
+ $line[] = "console.log(" . json_encode(var_export($m, true)) . ");";
+ }
+ break;
+ case '错误':
+ $msg = str_replace("\n", '\n', addslashes(is_scalar($m) ? $m : json_encode($m)));
+ $style = 'color:#F4006B;font-size:14px;';
+ $line[] = "console.error(\"%c{$msg}\", \"{$style}\");";
+ break;
+ case 'sql':
+ $msg = str_replace("\n", '\n', addslashes($m));
+ $style = "color:#009bb4;";
+ $line[] = "console.log(\"%c{$msg}\", \"{$style}\");";
+ break;
+ default:
+ $m = is_string($key) ? $key . ' ' . $m : $key + 1 . ' ' . $m;
+ $msg = json_encode($m);
+ $line[] = "console.log({$msg});";
+ break;
+ }
+ }
+ $line[] = "console.groupEnd();";
+ return implode(PHP_EOL, $line);
+ }
+
+ /**
+ * 获取文件加载信息
+ * @access protected
+ * @return integer|array
+ */
+ protected function getFileInfo()
+ {
+ $files = get_included_files();
+ $info = [];
+
+ foreach ($files as $key => $file) {
+ $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )';
+ }
+
+ return $info;
+ }
+}
diff --git a/thinkphp/library/think/debug/Html.php b/vendor/topthink/think-trace/src/Html.php
old mode 100755
new mode 100644
similarity index 55%
rename from thinkphp/library/think/debug/Html.php
rename to vendor/topthink/think-trace/src/Html.php
index a123762ee..bcb2f50d8
--- a/thinkphp/library/think/debug/Html.php
+++ b/vendor/topthink/think-trace/src/Html.php
@@ -1,106 +1,126 @@
-
-// +----------------------------------------------------------------------
-
-namespace think\debug;
-
-use think\Container;
-use think\Db;
-use think\Response;
-
-/**
- * 页面Trace调试
- */
-class Html
-{
- protected $config = [
- 'file' => '',
- 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'],
- ];
-
- // 实例化并传入参数
- public function __construct(array $config = [])
- {
- $this->config = array_merge($this->config, $config);
- }
-
- /**
- * 调试输出接口
- * @access public
- * @param Response $response Response对象
- * @param array $log 日志信息
- * @return bool
- */
- public function output(Response $response, array $log = [])
- {
- $request = Container::get('request');
- $contentType = $response->getHeader('Content-Type');
- $accept = $request->header('accept');
- if (strpos($accept, 'application/json') === 0 || $request->isAjax()) {
- return false;
- } elseif (!empty($contentType) && strpos($contentType, 'html') === false) {
- return false;
- }
- // 获取基本信息
- $runtime = number_format(microtime(true) - Container::get('app')->getBeginTime(), 10, '.', '');
- $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞';
- $mem = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2);
-
- // 页面Trace信息
- if ($request->host()) {
- $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true);
- } else {
- $uri = 'cmd:' . implode(' ', $_SERVER['argv']);
- }
- $base = [
- '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri,
- '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()),
- '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ',
- '缓存信息' => Container::get('cache')->getReadTimes() . ' reads,' . Container::get('cache')->getWriteTimes() . ' writes',
- ];
-
- if (session_id()) {
- $base['会话信息'] = 'SESSION_ID=' . session_id();
- }
-
- $info = Container::get('debug')->getFile(true);
-
- // 页面Trace信息
- $trace = [];
- foreach ($this->config['tabs'] as $name => $title) {
- $name = strtolower($name);
- switch ($name) {
- case 'base': // 基本信息
- $trace[$title] = $base;
- break;
- case 'file': // 文件信息
- $trace[$title] = $info;
- break;
- default: // 调试信息
- if (strpos($name, '|')) {
- // 多组信息
- $names = explode('|', $name);
- $result = [];
- foreach ($names as $name) {
- $result = array_merge($result, isset($log[$name]) ? $log[$name] : []);
- }
- $trace[$title] = $result;
- } else {
- $trace[$title] = isset($log[$name]) ? $log[$name] : '';
- }
- }
- }
- // 调用Trace页面模板
- ob_start();
- include $this->config['file'];
- return ob_get_clean();
- }
-
-}
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+namespace think\trace;
+
+use think\App;
+use think\Response;
+
+/**
+ * 页面Trace调试
+ */
+class Html
+{
+ protected $config = [
+ 'file' => '',
+ 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'],
+ ];
+
+ // 实例化并传入参数
+ public function __construct(array $config = [])
+ {
+ $this->config = array_merge($this->config, $config);
+ }
+
+ /**
+ * 调试输出接口
+ * @access public
+ * @param App $app 应用实例
+ * @param Response $response Response对象
+ * @param array $log 日志信息
+ * @return bool|string
+ */
+ public function output(App $app, Response $response, array $log = [])
+ {
+ $request = $app->request;
+ $contentType = $response->getHeader('Content-Type');
+
+ if ($request->isJson() || $request->isAjax()) {
+ return false;
+ } elseif (!empty($contentType) && strpos($contentType, 'html') === false) {
+ return false;
+ } elseif ($response->getCode() == 204) {
+ return false;
+ }
+
+ // 获取基本信息
+ $runtime = number_format(microtime(true) - $app->getBeginTime(), 10, '.', '');
+ $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞';
+ $mem = number_format((memory_get_usage() - $app->getBeginMem()) / 1024, 2);
+
+ // 页面Trace信息
+ if ($request->host()) {
+ $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true);
+ } else {
+ $uri = 'cmd:' . implode(' ', $_SERVER['argv']);
+ }
+
+ $base = [
+ '请求信息' => date('Y-m-d H:i:s', $request->time() ?: time()) . ' ' . $uri,
+ '运行时间' => number_format((float) $runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()),
+ '查询信息' => $app->db->getQueryTimes() . ' queries',
+ '缓存信息' => $app->cache->getReadTimes() . ' reads,' . $app->cache->getWriteTimes() . ' writes',
+ ];
+
+ if (isset($app->session)) {
+ $base['会话信息'] = 'SESSION_ID=' . $app->session->getId();
+ }
+
+ $info = $this->getFileInfo();
+
+ // 页面Trace信息
+ $trace = [];
+ foreach ($this->config['tabs'] as $name => $title) {
+ $name = strtolower($name);
+ switch ($name) {
+ case 'base': // 基本信息
+ $trace[$title] = $base;
+ break;
+ case 'file': // 文件信息
+ $trace[$title] = $info;
+ break;
+ default: // 调试信息
+ if (strpos($name, '|')) {
+ // 多组信息
+ $names = explode('|', $name);
+ $result = [];
+ foreach ($names as $item) {
+ $result = array_merge($result, $log[$item] ?? []);
+ }
+ $trace[$title] = $result;
+ } else {
+ $trace[$title] = $log[$name] ?? '';
+ }
+ }
+ }
+ // 调用Trace页面模板
+ ob_start();
+ include $this->config['file'] ?: __DIR__ . '/tpl/page_trace.tpl';
+ return ob_get_clean();
+ }
+
+ /**
+ * 获取文件加载信息
+ * @access protected
+ * @return integer|array
+ */
+ protected function getFileInfo()
+ {
+ $files = get_included_files();
+ $info = [];
+
+ foreach ($files as $key => $file) {
+ $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )';
+ }
+
+ return $info;
+ }
+}
diff --git a/vendor/topthink/think-trace/src/Service.php b/vendor/topthink/think-trace/src/Service.php
new file mode 100644
index 000000000..3e78ecc72
--- /dev/null
+++ b/vendor/topthink/think-trace/src/Service.php
@@ -0,0 +1,21 @@
+
+// +----------------------------------------------------------------------
+namespace think\trace;
+
+use think\Service as BaseService;
+
+class Service extends BaseService
+{
+ public function register()
+ {
+ $this->app->middleware->add(TraceDebug::class);
+ }
+}
diff --git a/vendor/topthink/think-trace/src/TraceDebug.php b/vendor/topthink/think-trace/src/TraceDebug.php
new file mode 100644
index 000000000..5ed9cbfcf
--- /dev/null
+++ b/vendor/topthink/think-trace/src/TraceDebug.php
@@ -0,0 +1,109 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\trace;
+
+use Closure;
+use think\App;
+use think\Config;
+use think\event\LogWrite;
+use think\Request;
+use think\Response;
+use think\response\Redirect;
+
+/**
+ * 页面Trace中间件
+ */
+class TraceDebug
+{
+
+ /**
+ * Trace日志
+ * @var array
+ */
+ protected $log = [];
+
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [];
+
+ /** @var App */
+ protected $app;
+
+ public function __construct(App $app, Config $config)
+ {
+ $this->app = $app;
+ $this->config = $config->get('trace');
+ }
+
+ /**
+ * 页面Trace调试
+ * @access public
+ * @param Request $request
+ * @param Closure $next
+ * @return void
+ */
+ public function handle($request, Closure $next)
+ {
+ $debug = $this->app->isDebug();
+
+ // 注册日志监听
+ if ($debug) {
+ $this->log = [];
+ $this->app->event->listen(LogWrite::class, function ($event) {
+ if (empty($this->config['channel']) || $this->config['channel'] == $event->channel) {
+ $this->log = array_merge_recursive($this->log, $event->log);
+ }
+ });
+ }
+
+ $response = $next($request);
+
+ // Trace调试注入
+ if ($debug) {
+ $data = $response->getContent();
+ $this->traceDebug($response, $data);
+ $response->content($data);
+ }
+
+ return $response;
+ }
+
+ public function traceDebug(Response $response, &$content)
+ {
+ $config = $this->config;
+ $type = $config['type'] ?? 'Html';
+
+ unset($config['type']);
+
+ $trace = App::factory($type, '\\think\\trace\\', $config);
+
+ if ($response instanceof Redirect) {
+ //TODO 记录
+ } else {
+ $log = $this->app->log->getLog($config['channel'] ?? '');
+ $log = array_merge_recursive($this->log, $log);
+ $output = $trace->output($this->app, $response, $log);
+ if (is_string($output)) {
+ // trace调试信息注入
+ $pos = strripos($content, '');
+ if (false !== $pos) {
+ $content = substr($content, 0, $pos) . $output . substr($content, $pos);
+ } else {
+ $content = $content . $output;
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/topthink/think-trace/src/config.php b/vendor/topthink/think-trace/src/config.php
new file mode 100644
index 000000000..fad2392d9
--- /dev/null
+++ b/vendor/topthink/think-trace/src/config.php
@@ -0,0 +1,10 @@
+ 'Html',
+ // 读取的日志通道名
+ 'channel' => '',
+];
diff --git a/thinkphp/tpl/page_trace.tpl b/vendor/topthink/think-trace/src/tpl/page_trace.tpl
old mode 100755
new mode 100644
similarity index 98%
rename from thinkphp/tpl/page_trace.tpl
rename to vendor/topthink/think-trace/src/tpl/page_trace.tpl
index 2e5afbab9..6b1b9a12f
--- a/thinkphp/tpl/page_trace.tpl
+++ b/vendor/topthink/think-trace/src/tpl/page_trace.tpl
@@ -24,7 +24,7 @@
-
getUseTime().'s ';?>
+
diff --git a/vendor/topthink/think-view/.gitignore b/vendor/topthink/think-view/.gitignore
new file mode 100644
index 000000000..485dee64b
--- /dev/null
+++ b/vendor/topthink/think-view/.gitignore
@@ -0,0 +1 @@
+.idea
diff --git a/vendor/topthink/think-view/LICENSE b/vendor/topthink/think-view/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/vendor/topthink/think-view/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/topthink/think-view/README.md b/vendor/topthink/think-view/README.md
new file mode 100644
index 000000000..4e52defd5
--- /dev/null
+++ b/vendor/topthink/think-view/README.md
@@ -0,0 +1,36 @@
+# think-view
+
+ThinkPHP6.0 Think-Template模板引擎驱动
+
+
+## 安装
+
+~~~php
+composer require topthink/think-view
+~~~
+
+## 用法示例
+
+本扩展不能单独使用,依赖ThinkPHP6.0+
+
+首先配置config目录下的template.php配置文件,然后可以按照下面的用法使用。
+
+~~~php
+
+use think\facade\View;
+
+// 模板变量赋值和渲染输出
+View::assign(['name' => 'think'])
+ // 输出过滤
+ ->filter(function($content){
+ return str_replace('search', 'replace', $content);
+ })
+ // 读取模板文件渲染输出
+ ->fetch('index');
+
+
+// 或者使用助手函数
+view('index', ['name' => 'think']);
+~~~
+
+具体的模板引擎配置请参考think-template库。
\ No newline at end of file
diff --git a/vendor/topthink/think-view/composer.json b/vendor/topthink/think-view/composer.json
new file mode 100644
index 000000000..f4e6431bd
--- /dev/null
+++ b/vendor/topthink/think-view/composer.json
@@ -0,0 +1,20 @@
+{
+ "name": "topthink/think-view",
+ "description": "thinkphp template driver",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "liu21st",
+ "email": "liu21st@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=7.1.0",
+ "topthink/think-template": "^2.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "think\\view\\driver\\": "src"
+ }
+ }
+}
diff --git a/vendor/topthink/think-view/src/Think.php b/vendor/topthink/think-view/src/Think.php
new file mode 100644
index 000000000..562b54aec
--- /dev/null
+++ b/vendor/topthink/think-view/src/Think.php
@@ -0,0 +1,259 @@
+
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\view\driver;
+
+use think\App;
+use think\helper\Str;
+use think\Template;
+use think\template\exception\TemplateNotFoundException;
+
+class Think
+{
+ // 模板引擎实例
+ private $template;
+ private $app;
+
+ // 模板引擎参数
+ protected $config = [
+ // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法
+ 'auto_rule' => 1,
+ // 视图目录名
+ 'view_dir_name' => 'view',
+ // 模板起始路径
+ 'view_path' => '',
+ // 模板文件后缀
+ 'view_suffix' => 'html',
+ // 模板文件名分隔符
+ 'view_depr' => DIRECTORY_SEPARATOR,
+ // 是否开启模板编译缓存,设为false则每次都会重新编译
+ 'tpl_cache' => true,
+ ];
+
+ public function __construct(App $app, array $config = [])
+ {
+ $this->app = $app;
+
+ $this->config = array_merge($this->config, (array) $config);
+
+ if (empty($this->config['cache_path'])) {
+ $this->config['cache_path'] = $app->getRuntimePath() . 'temp' . DIRECTORY_SEPARATOR;
+ }
+
+ $this->template = new Template($this->config);
+ $this->template->setCache($app->cache);
+ $this->template->extend('$Think', function (array $vars) {
+ $type = strtoupper(trim(array_shift($vars)));
+ $param = implode('.', $vars);
+
+ switch ($type) {
+ case 'CONST':
+ $parseStr = strtoupper($param);
+ break;
+ case 'CONFIG':
+ $parseStr = 'config(\'' . $param . '\')';
+ break;
+ case 'LANG':
+ $parseStr = 'lang(\'' . $param . '\')';
+ break;
+ case 'NOW':
+ $parseStr = "date('Y-m-d g:i a',time())";
+ break;
+ case 'LDELIM':
+ $parseStr = '\'' . ltrim($this->getConfig('tpl_begin'), '\\') . '\'';
+ break;
+ case 'RDELIM':
+ $parseStr = '\'' . ltrim($this->getConfig('tpl_end'), '\\') . '\'';
+ break;
+ default:
+ $parseStr = defined($type) ? $type : '\'\'';
+ }
+
+ return $parseStr;
+ });
+
+ $this->template->extend('$Request', function (array $vars) {
+ // 获取Request请求对象参数
+ $method = array_shift($vars);
+ if (!empty($vars)) {
+ $params = implode('.', $vars);
+ if ('true' != $params) {
+ $params = '\'' . $params . '\'';
+ }
+ } else {
+ $params = '';
+ }
+
+ return 'app(\'request\')->' . $method . '(' . $params . ')';
+ });
+ }
+
+ /**
+ * 检测是否存在模板文件
+ * @access public
+ * @param string $template 模板文件或者模板规则
+ * @return bool
+ */
+ public function exists(string $template): bool
+ {
+ if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
+ // 获取模板文件名
+ $template = $this->parseTemplate($template);
+ }
+
+ return is_file($template);
+ }
+
+ /**
+ * 渲染模板文件
+ * @access public
+ * @param string $template 模板文件
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function fetch(string $template, array $data = []): void
+ {
+ if (empty($this->config['view_path'])) {
+ $view = $this->config['view_dir_name'];
+
+ if (is_dir($this->app->getAppPath() . $view)) {
+ $path = $this->app->getAppPath() . $view . DIRECTORY_SEPARATOR;
+ } else {
+ $appName = $this->app->http->getName();
+ $path = $this->app->getRootPath() . $view . DIRECTORY_SEPARATOR . ($appName ? $appName . DIRECTORY_SEPARATOR : '');
+ }
+
+ $this->config['view_path'] = $path;
+ $this->template->view_path = $path;
+ }
+
+ if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
+ // 获取模板文件名
+ $template = $this->parseTemplate($template);
+ }
+
+ // 模板不存在 抛出异常
+ if (!is_file($template)) {
+ throw new TemplateNotFoundException('template not exists:' . $template, $template);
+ }
+
+ $this->template->fetch($template, $data);
+ }
+
+ /**
+ * 渲染模板内容
+ * @access public
+ * @param string $template 模板内容
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function display(string $template, array $data = []): void
+ {
+ $this->template->display($template, $data);
+ }
+
+ /**
+ * 自动定位模板文件
+ * @access private
+ * @param string $template 模板文件规则
+ * @return string
+ */
+ private function parseTemplate(string $template): string
+ {
+ // 分析模板文件规则
+ $request = $this->app['request'];
+
+ // 获取视图根目录
+ if (strpos($template, '@')) {
+ // 跨模块调用
+ list($app, $template) = explode('@', $template);
+ }
+
+ if (isset($app)) {
+ $view = $this->config['view_dir_name'];
+ $viewPath = $this->app->getBasePath() . $app . DIRECTORY_SEPARATOR . $view . DIRECTORY_SEPARATOR;
+
+ if (is_dir($viewPath)) {
+ $path = $viewPath;
+ } else {
+ $path = $this->app->getRootPath() . $view . DIRECTORY_SEPARATOR . $app . DIRECTORY_SEPARATOR;
+ }
+
+ $this->template->view_path = $path;
+ } else {
+ $path = $this->config['view_path'];
+ }
+
+ $depr = $this->config['view_depr'];
+
+ if (0 !== strpos($template, '/')) {
+ $template = str_replace(['/', ':'], $depr, $template);
+ $controller = $request->controller();
+
+ if (strpos($controller, '.')) {
+ $pos = strrpos($controller, '.');
+ $controller = substr($controller, 0, $pos) . '.' . Str::snake(substr($controller, $pos + 1));
+ } else {
+ $controller = Str::snake($controller);
+ }
+
+ if ($controller) {
+ if ('' == $template) {
+ // 如果模板文件名为空 按照默认模板渲染规则定位
+ if (2 == $this->config['auto_rule']) {
+ $template = $request->action(true);
+ } elseif (3 == $this->config['auto_rule']) {
+ $template = $request->action();
+ } else {
+ $template = Str::snake($request->action());
+ }
+
+ $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template;
+ } elseif (false === strpos($template, $depr)) {
+ $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template;
+ }
+ }
+ } else {
+ $template = str_replace(['/', ':'], $depr, substr($template, 1));
+ }
+
+ return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.');
+ }
+
+ /**
+ * 配置模板引擎
+ * @access private
+ * @param array $config 参数
+ * @return void
+ */
+ public function config(array $config): void
+ {
+ $this->template->config($config);
+ $this->config = array_merge($this->config, $config);
+ }
+
+ /**
+ * 获取模板引擎配置
+ * @access public
+ * @param string $name 参数名
+ * @return void
+ */
+ public function getConfig(string $name)
+ {
+ return $this->template->getConfig($name);
+ }
+
+ public function __call($method, $params)
+ {
+ return call_user_func_array([$this->template, $method], $params);
+ }
+}