更新vendor和首台首页

This commit is contained in:
muzi_ys 2022-08-28 22:21:06 +08:00
parent ecdf50d355
commit 3b715806fd
64 changed files with 497 additions and 161 deletions

View File

@ -51,6 +51,12 @@ class Index extends AuthController
$this->assign("friend_link_count", $FriendLinkCount); $this->assign("friend_link_count", $FriendLinkCount);
$messageFormCount = MessageForm::counts(new MessageForm()); $messageFormCount = MessageForm::counts(new MessageForm());
$this->assign("message_form_count", $messageFormCount); $this->assign("message_form_count", $messageFormCount);
$data =[
"page"=> 0 ,
"limit" => 5
];
$articleList = Document::systemPage($data)['data'];
$this->assign("article_list", $articleList);
return $this->fetch(); return $this->fetch();
} }

View File

@ -69,6 +69,40 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<h4>项目信息</h4>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>文章标题</th>
<th>文章分类</th>
<th>发布时间</th>
</tr>
</thead>
<tbody>
{volist name="article_list" id="vo"}
<tr>
<td>{$vo.id}</td>
<td>{$vo.title}</td>
<td>{$vo.category_title}</td>
<td>{$vo.create_time}</td>
</tr>
{/volist}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div> </div>
<script type="text/javascript" src="/static/admin/js/jquery.min.js"></script> <script type="text/javascript" src="/static/admin/js/jquery.min.js"></script>

View File

@ -33,10 +33,10 @@ class Document extends BaseModel
{ {
$model = new self; $model = new self;
$model = $model->where("type", "=", $where['type'] ?? Data::DOCUMENT_TYPE_ARTICLE); $model = $model->where("type", "=", $where['type'] ?? Data::DOCUMENT_TYPE_ARTICLE);
if ($where['title'] != '') $model = $model->where("title", "like", "%$where[title]%"); if (!empty($where['title'])) $model = $model->where("title", "like", "%$where[title]%");
if ($where['start_time'] != '') $model = $model->where("create_time", ">", strtotime($where['start_time'] . " 00:00:00")); if (!empty($where['start_time'])) $model = $model->where("create_time", ">", strtotime($where['start_time'] . " 00:00:00"));
if ($where['end_time'] != '') $model = $model->where("create_time", "<", strtotime($where['end_time'] . " 23:59:59")); if (!empty($where['end_time'])) $model = $model->where("create_time", "<", strtotime($where['end_time'] . " 23:59:59"));
if ($where['status'] != '') $model = $model->where("status", $where['status']); if (!empty($where['status'])) $model = $model->where("status", $where['status']);
$model = $model->order("sort desc")->order("id desc"); $model = $model->order("sort desc")->order("id desc");
$count = self::counts($model); $count = self::counts($model);
if ($where['page'] && $where['limit']) $model = $model->page((int)$where['page'], (int)$where['limit']); if ($where['page'] && $where['limit']) $model = $model->page((int)$where['page'], (int)$where['limit']);

View File

@ -108,7 +108,10 @@ if (PHP_VERSION_ID < 80000) {
} }
} }
if (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) { if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server'); include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server');
exit(0); exit(0);
} }

View File

@ -1,5 +1,5 @@
@ECHO OFF @ECHO OFF
setlocal DISABLEDELAYEDEXPANSION setlocal DISABLEDELAYEDEXPANSION
SET BIN_TARGET=%~dp0/var-dump-server SET BIN_TARGET=%~dp0/var-dump-server
SET COMPOSER_BIN_DIR=%~dp0 SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
php "%BIN_TARGET%" %* php "%BIN_TARGET%" %*

View File

@ -9,7 +9,7 @@ return array(
'think\\view\\driver\\' => array($vendorDir . '/topthink/think-view/src'), 'think\\view\\driver\\' => array($vendorDir . '/topthink/think-view/src'),
'think\\captcha\\' => array($vendorDir . '/topthink/think-captcha/src'), 'think\\captcha\\' => array($vendorDir . '/topthink/think-captcha/src'),
'think\\app\\' => array($vendorDir . '/topthink/think-multi-app/src'), 'think\\app\\' => array($vendorDir . '/topthink/think-multi-app/src'),
'think\\' => array($vendorDir . '/topthink/framework/src/think', $vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-template/src', $vendorDir . '/topthink/think-orm/src'), 'think\\' => array($vendorDir . '/topthink/framework/src/think', $vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src', $vendorDir . '/topthink/think-template/src'),
'app\\' => array($baseDir . '/app'), 'app\\' => array($baseDir . '/app'),
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'), 'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),

View File

@ -81,8 +81,8 @@ class ComposerStaticInit4b57298e8d0e895486f3307a354a7e1a
array ( array (
0 => __DIR__ . '/..' . '/topthink/framework/src/think', 0 => __DIR__ . '/..' . '/topthink/framework/src/think',
1 => __DIR__ . '/..' . '/topthink/think-helper/src', 1 => __DIR__ . '/..' . '/topthink/think-helper/src',
2 => __DIR__ . '/..' . '/topthink/think-template/src', 2 => __DIR__ . '/..' . '/topthink/think-orm/src',
3 => __DIR__ . '/..' . '/topthink/think-orm/src', 3 => __DIR__ . '/..' . '/topthink/think-template/src',
), ),
'app\\' => 'app\\' =>
array ( array (

View File

@ -915,17 +915,17 @@
}, },
{ {
"name": "symfony/http-foundation", "name": "symfony/http-foundation",
"version": "v5.4.10", "version": "v5.4.12",
"version_normalized": "5.4.10.0", "version_normalized": "5.4.12.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/http-foundation.git", "url": "https://github.com/symfony/http-foundation.git",
"reference": "e7793b7906f72a8cc51054fbca9dcff7a8af1c1e" "reference": "f4bfe9611b113b15d98a43da68ec9b5a00d56791"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/e7793b7906f72a8cc51054fbca9dcff7a8af1c1e", "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f4bfe9611b113b15d98a43da68ec9b5a00d56791",
"reference": "e7793b7906f72a8cc51054fbca9dcff7a8af1c1e", "reference": "f4bfe9611b113b15d98a43da68ec9b5a00d56791",
"shasum": "", "shasum": "",
"mirrors": [ "mirrors": [
{ {
@ -943,13 +943,16 @@
"require-dev": { "require-dev": {
"predis/predis": "~1.0", "predis/predis": "~1.0",
"symfony/cache": "^4.4|^5.0|^6.0", "symfony/cache": "^4.4|^5.0|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/expression-language": "^4.4|^5.0|^6.0", "symfony/expression-language": "^4.4|^5.0|^6.0",
"symfony/mime": "^4.4|^5.0|^6.0" "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4",
"symfony/mime": "^4.4|^5.0|^6.0",
"symfony/rate-limiter": "^5.2|^6.0"
}, },
"suggest": { "suggest": {
"symfony/mime": "To use the file extension guesser" "symfony/mime": "To use the file extension guesser"
}, },
"time": "2022-06-19T13:13:40+00:00", "time": "2022-08-19T07:33:17+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@ -977,7 +980,7 @@
"description": "Defines an object-oriented layer for the HTTP specification", "description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/http-foundation/tree/v5.4.10" "source": "https://github.com/symfony/http-foundation/tree/v5.4.12"
}, },
"funding": [ "funding": [
{ {
@ -1266,17 +1269,17 @@
}, },
{ {
"name": "symfony/var-dumper", "name": "symfony/var-dumper",
"version": "v4.4.42", "version": "v4.4.44",
"version_normalized": "4.4.42.0", "version_normalized": "4.4.44.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/var-dumper.git", "url": "https://github.com/symfony/var-dumper.git",
"reference": "742aab50ad097bcb62d91fccb613f66b8047d2ca" "reference": "f19951007dae942cc79b979c1fe26bfdfbeb54ed"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/742aab50ad097bcb62d91fccb613f66b8047d2ca", "url": "https://api.github.com/repos/symfony/var-dumper/zipball/f19951007dae942cc79b979c1fe26bfdfbeb54ed",
"reference": "742aab50ad097bcb62d91fccb613f66b8047d2ca", "reference": "f19951007dae942cc79b979c1fe26bfdfbeb54ed",
"shasum": "", "shasum": "",
"mirrors": [ "mirrors": [
{ {
@ -1306,7 +1309,7 @@
"ext-intl": "To show region name in time zone dump", "ext-intl": "To show region name in time zone dump",
"symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
}, },
"time": "2022-05-21T10:00:54+00:00", "time": "2022-07-20T09:59:04+00:00",
"bin": [ "bin": [
"Resources/bin/var-dump-server" "Resources/bin/var-dump-server"
], ],
@ -1344,7 +1347,7 @@
"dump" "dump"
], ],
"support": { "support": {
"source": "https://github.com/symfony/var-dumper/tree/v4.4.42" "source": "https://github.com/symfony/var-dumper/tree/v4.4.44"
}, },
"funding": [ "funding": [
{ {
@ -1364,17 +1367,17 @@
}, },
{ {
"name": "topthink/framework", "name": "topthink/framework",
"version": "v6.0.12", "version": "v6.0.13",
"version_normalized": "6.0.12.0", "version_normalized": "6.0.13.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/top-think/framework.git", "url": "https://github.com/top-think/framework.git",
"reference": "e478316ac843c1a884a3b3a7a94db17c4001ff5c" "reference": "126d5b2cbacb73d6e2a85cbc7a2c6ee59d0b3fa6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/top-think/framework/zipball/e478316ac843c1a884a3b3a7a94db17c4001ff5c", "url": "https://api.github.com/repos/top-think/framework/zipball/126d5b2cbacb73d6e2a85cbc7a2c6ee59d0b3fa6",
"reference": "e478316ac843c1a884a3b3a7a94db17c4001ff5c", "reference": "126d5b2cbacb73d6e2a85cbc7a2c6ee59d0b3fa6",
"shasum": "", "shasum": "",
"mirrors": [ "mirrors": [
{ {
@ -1402,7 +1405,7 @@
"mockery/mockery": "^1.2", "mockery/mockery": "^1.2",
"phpunit/phpunit": "^7.0" "phpunit/phpunit": "^7.0"
}, },
"time": "2022-01-21T06:31:07+00:00", "time": "2022-07-15T02:52:08+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@ -1434,7 +1437,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/top-think/framework/issues", "issues": "https://github.com/top-think/framework/issues",
"source": "https://github.com/top-think/framework/tree/v6.0.12" "source": "https://github.com/top-think/framework/tree/v6.0.13"
}, },
"install-path": "../topthink/framework" "install-path": "../topthink/framework"
}, },
@ -1614,17 +1617,17 @@
}, },
{ {
"name": "topthink/think-orm", "name": "topthink/think-orm",
"version": "v2.0.53", "version": "v2.0.54",
"version_normalized": "2.0.53.0", "version_normalized": "2.0.54.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/top-think/think-orm.git", "url": "https://github.com/top-think/think-orm.git",
"reference": "06783eda65547a70ea686360a897759e1f873fff" "reference": "97b061b47616301ff29fbd4c35ed9184e1162e4e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/top-think/think-orm/zipball/06783eda65547a70ea686360a897759e1f873fff", "url": "https://api.github.com/repos/top-think/think-orm/zipball/97b061b47616301ff29fbd4c35ed9184e1162e4e",
"reference": "06783eda65547a70ea686360a897759e1f873fff", "reference": "97b061b47616301ff29fbd4c35ed9184e1162e4e",
"shasum": "", "shasum": "",
"mirrors": [ "mirrors": [
{ {
@ -1637,14 +1640,14 @@
"ext-json": "*", "ext-json": "*",
"ext-pdo": "*", "ext-pdo": "*",
"php": ">=7.1.0", "php": ">=7.1.0",
"psr/log": "~1.0", "psr/log": "^1.0|^2.0",
"psr/simple-cache": "^1.0", "psr/simple-cache": "^1.0|^2.0",
"topthink/think-helper": "^3.1" "topthink/think-helper": "^3.1"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^7|^8|^9.5" "phpunit/phpunit": "^7|^8|^9.5"
}, },
"time": "2022-02-28T14:54:22+00:00", "time": "2022-07-05T05:25:51+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@ -1672,7 +1675,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/top-think/think-orm/issues", "issues": "https://github.com/top-think/think-orm/issues",
"source": "https://github.com/top-think/think-orm/tree/v2.0.53" "source": "https://github.com/top-think/think-orm/tree/v2.0.54"
}, },
"install-path": "../topthink/think-orm" "install-path": "../topthink/think-orm"
}, },

View File

@ -3,7 +3,7 @@
'name' => 'topthink/think', 'name' => 'topthink/think',
'pretty_version' => 'dev-master', 'pretty_version' => 'dev-master',
'version' => 'dev-master', 'version' => 'dev-master',
'reference' => 'dc0fcfd1eb5f19c42b2ec51208d3bca840538c92', 'reference' => 'ecdf50d35553ba71e48f82e41d38feb090fd0f7b',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@ -128,9 +128,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/http-foundation' => array( 'symfony/http-foundation' => array(
'pretty_version' => 'v5.4.10', 'pretty_version' => 'v5.4.12',
'version' => '5.4.10.0', 'version' => '5.4.12.0',
'reference' => 'e7793b7906f72a8cc51054fbca9dcff7a8af1c1e', 'reference' => 'f4bfe9611b113b15d98a43da68ec9b5a00d56791',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/http-foundation', 'install_path' => __DIR__ . '/../symfony/http-foundation',
'aliases' => array(), 'aliases' => array(),
@ -164,18 +164,18 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/var-dumper' => array( 'symfony/var-dumper' => array(
'pretty_version' => 'v4.4.42', 'pretty_version' => 'v4.4.44',
'version' => '4.4.42.0', 'version' => '4.4.44.0',
'reference' => '742aab50ad097bcb62d91fccb613f66b8047d2ca', 'reference' => 'f19951007dae942cc79b979c1fe26bfdfbeb54ed',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/var-dumper', 'install_path' => __DIR__ . '/../symfony/var-dumper',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'topthink/framework' => array( 'topthink/framework' => array(
'pretty_version' => 'v6.0.12', 'pretty_version' => 'v6.0.13',
'version' => '6.0.12.0', 'version' => '6.0.13.0',
'reference' => 'e478316ac843c1a884a3b3a7a94db17c4001ff5c', 'reference' => '126d5b2cbacb73d6e2a85cbc7a2c6ee59d0b3fa6',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../topthink/framework', 'install_path' => __DIR__ . '/../topthink/framework',
'aliases' => array(), 'aliases' => array(),
@ -184,7 +184,7 @@
'topthink/think' => array( 'topthink/think' => array(
'pretty_version' => 'dev-master', 'pretty_version' => 'dev-master',
'version' => 'dev-master', 'version' => 'dev-master',
'reference' => 'dc0fcfd1eb5f19c42b2ec51208d3bca840538c92', 'reference' => 'ecdf50d35553ba71e48f82e41d38feb090fd0f7b',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@ -218,9 +218,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'topthink/think-orm' => array( 'topthink/think-orm' => array(
'pretty_version' => 'v2.0.53', 'pretty_version' => 'v2.0.54',
'version' => '2.0.53.0', 'version' => '2.0.54.0',
'reference' => '06783eda65547a70ea686360a897759e1f873fff', 'reference' => '97b061b47616301ff29fbd4c35ed9184e1162e4e',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../topthink/think-orm', 'install_path' => __DIR__ . '/../topthink/think-orm',
'aliases' => array(), 'aliases' => array(),

2
vendor/services.php vendored
View File

@ -1,5 +1,5 @@
<?php <?php
// This file is automatically generated at:2022-07-10 21:24:02 // This file is automatically generated at:2022-08-28 11:10:37
declare (strict_types = 1); declare (strict_types = 1);
return array ( return array (
0 => 'think\\captcha\\CaptchaService', 0 => 'think\\captcha\\CaptchaService',

View File

@ -34,6 +34,7 @@ class BinaryFileResponse extends Response
protected $offset = 0; protected $offset = 0;
protected $maxlen = -1; protected $maxlen = -1;
protected $deleteFileAfterSend = false; protected $deleteFileAfterSend = false;
protected $chunkSize = 8 * 1024;
/** /**
* @param \SplFileInfo|string $file The file to stream * @param \SplFileInfo|string $file The file to stream
@ -125,6 +126,22 @@ class BinaryFileResponse extends Response
return $this->file; return $this->file;
} }
/**
* Sets the response stream chunk size.
*
* @return $this
*/
public function setChunkSize(int $chunkSize): self
{
if ($chunkSize < 1 || $chunkSize > \PHP_INT_MAX) {
throw new \LogicException('The chunk size of a BinaryFileResponse cannot be less than 1 or greater than PHP_INT_MAX.');
}
$this->chunkSize = $chunkSize;
return $this;
}
/** /**
* Automatically sets the Last-Modified header according the file modification date. * Automatically sets the Last-Modified header according the file modification date.
* *
@ -306,7 +323,23 @@ class BinaryFileResponse extends Response
$out = fopen('php://output', 'w'); $out = fopen('php://output', 'w');
$file = fopen($this->file->getPathname(), 'r'); $file = fopen($this->file->getPathname(), 'r');
stream_copy_to_stream($file, $out, $this->maxlen, $this->offset); ignore_user_abort(true);
if (0 !== $this->offset) {
fseek($file, $this->offset);
}
$length = $this->maxlen;
while ($length && !feof($file)) {
$read = ($length > $this->chunkSize) ? $this->chunkSize : $length;
$length -= $read;
stream_copy_to_stream($file, $out, $read);
if (connection_aborted()) {
break;
}
}
fclose($out); fclose($out);
fclose($file); fclose($file);

View File

@ -223,8 +223,8 @@ class UploadedFile extends File
*/ */
public static function getMaxFilesize() public static function getMaxFilesize()
{ {
$sizePostMax = self::parseFilesize(ini_get('post_max_size')); $sizePostMax = self::parseFilesize(\ini_get('post_max_size'));
$sizeUploadMax = self::parseFilesize(ini_get('upload_max_filesize')); $sizeUploadMax = self::parseFilesize(\ini_get('upload_max_filesize'));
return min($sizePostMax ?: \PHP_INT_MAX, $sizeUploadMax ?: \PHP_INT_MAX); return min($sizePostMax ?: \PHP_INT_MAX, $sizeUploadMax ?: \PHP_INT_MAX);
} }
@ -253,8 +253,11 @@ class UploadedFile extends File
switch (substr($size, -1)) { switch (substr($size, -1)) {
case 't': $max *= 1024; case 't': $max *= 1024;
// no break
case 'g': $max *= 1024; case 'g': $max *= 1024;
// no break
case 'm': $max *= 1024; case 'm': $max *= 1024;
// no break
case 'k': $max *= 1024; case 'k': $max *= 1024;
} }

View File

@ -29,14 +29,14 @@ final class InputBag extends ParameterBag
*/ */
public function get(string $key, $default = null) public function get(string $key, $default = null)
{ {
if (null !== $default && !is_scalar($default) && !(\is_object($default) && method_exists($default, '__toString'))) { if (null !== $default && !\is_scalar($default) && !(\is_object($default) && method_exists($default, '__toString'))) {
trigger_deprecation('symfony/http-foundation', '5.1', 'Passing a non-scalar value as 2nd argument to "%s()" is deprecated, pass a scalar or null instead.', __METHOD__); trigger_deprecation('symfony/http-foundation', '5.1', 'Passing a non-scalar value as 2nd argument to "%s()" is deprecated, pass a scalar or null instead.', __METHOD__);
} }
$value = parent::get($key, $this); $value = parent::get($key, $this);
if (null !== $value && $this !== $value && !is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { if (null !== $value && $this !== $value && !\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
trigger_deprecation('symfony/http-foundation', '5.1', 'Retrieving a non-string value from "%s()" is deprecated, and will throw a "%s" exception in Symfony 6.0, use "%s::all($key)" instead.', __METHOD__, BadRequestException::class, __CLASS__); trigger_deprecation('symfony/http-foundation', '5.1', 'Retrieving a non-scalar value from "%s()" is deprecated, and will throw a "%s" exception in Symfony 6.0, use "%s::all($key)" instead.', __METHOD__, BadRequestException::class, __CLASS__);
} }
return $this === $value ? $default : $value; return $this === $value ? $default : $value;
@ -76,7 +76,7 @@ final class InputBag extends ParameterBag
*/ */
public function set(string $key, $value) public function set(string $key, $value)
{ {
if (null !== $value && !is_scalar($value) && !\is_array($value) && !method_exists($value, '__toString')) { if (null !== $value && !\is_scalar($value) && !\is_array($value) && !method_exists($value, '__toString')) {
trigger_deprecation('symfony/http-foundation', '5.1', 'Passing "%s" as a 2nd Argument to "%s()" is deprecated, pass a scalar, array, or null instead.', get_debug_type($value), __METHOD__); trigger_deprecation('symfony/http-foundation', '5.1', 'Passing "%s" as a 2nd Argument to "%s()" is deprecated, pass a scalar, array, or null instead.', get_debug_type($value), __METHOD__);
} }

View File

@ -39,7 +39,7 @@ class ParameterBag implements \IteratorAggregate, \Countable
* *
* @return array * @return array
*/ */
public function all(/*string $key = null*/) public function all(/* string $key = null */)
{ {
$key = \func_num_args() > 0 ? func_get_arg(0) : null; $key = \func_num_args() > 0 ? func_get_arg(0) : null;

View File

@ -35,9 +35,7 @@ abstract class AbstractRequestRateLimiter implements RequestRateLimiterInterface
foreach ($limiters as $limiter) { foreach ($limiters as $limiter) {
$rateLimit = $limiter->consume(1); $rateLimit = $limiter->consume(1);
if (null === $minimalRateLimit || $rateLimit->getRemainingTokens() < $minimalRateLimit->getRemainingTokens()) { $minimalRateLimit = $minimalRateLimit ? self::getMinimalRateLimit($minimalRateLimit, $rateLimit) : $rateLimit;
$minimalRateLimit = $rateLimit;
}
} }
return $minimalRateLimit; return $minimalRateLimit;
@ -54,4 +52,20 @@ abstract class AbstractRequestRateLimiter implements RequestRateLimiterInterface
* @return LimiterInterface[] a set of limiters using keys extracted from the request * @return LimiterInterface[] a set of limiters using keys extracted from the request
*/ */
abstract protected function getLimiters(Request $request): array; abstract protected function getLimiters(Request $request): array;
private static function getMinimalRateLimit(RateLimit $first, RateLimit $second): RateLimit
{
if ($first->isAccepted() !== $second->isAccepted()) {
return $first->isAccepted() ? $second : $first;
}
$firstRemainingTokens = $first->getRemainingTokens();
$secondRemainingTokens = $second->getRemainingTokens();
if ($firstRemainingTokens === $secondRemainingTokens) {
return $first->getRetryAfter() < $second->getRetryAfter() ? $second : $first;
}
return $firstRemainingTokens > $secondRemainingTokens ? $second : $first;
}
} }

View File

@ -562,7 +562,7 @@ class Request
$request = ['g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE]; $request = ['g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE];
$requestOrder = ini_get('request_order') ?: ini_get('variables_order'); $requestOrder = \ini_get('request_order') ?: \ini_get('variables_order');
$requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp'; $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp';
$_REQUEST = [[]]; $_REQUEST = [[]];

View File

@ -72,7 +72,7 @@ class Response
public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585 public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
public const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585 public const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585
public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585 public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585
public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; // RFC7725
public const HTTP_INTERNAL_SERVER_ERROR = 500; public const HTTP_INTERNAL_SERVER_ERROR = 500;
public const HTTP_NOT_IMPLEMENTED = 501; public const HTTP_NOT_IMPLEMENTED = 501;
public const HTTP_BAD_GATEWAY = 502; public const HTTP_BAD_GATEWAY = 502;
@ -1245,6 +1245,7 @@ class Response
while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) { while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) {
if ($flush) { if ($flush) {
ob_end_flush(); ob_end_flush();
flush();
} else { } else {
ob_end_clean(); ob_end_clean();
} }

View File

@ -35,8 +35,8 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
public function open($savePath, $sessionName) public function open($savePath, $sessionName)
{ {
$this->sessionName = $sessionName; $this->sessionName = $sessionName;
if (!headers_sent() && !ini_get('session.cache_limiter') && '0' !== ini_get('session.cache_limiter')) { if (!headers_sent() && !\ini_get('session.cache_limiter') && '0' !== \ini_get('session.cache_limiter')) {
header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) ini_get('session.cache_expire'))); header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) \ini_get('session.cache_expire')));
} }
return true; return true;
@ -126,7 +126,7 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
#[\ReturnTypeWillChange] #[\ReturnTypeWillChange]
public function destroy($sessionId) public function destroy($sessionId)
{ {
if (!headers_sent() && filter_var(ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN)) { if (!headers_sent() && filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN)) {
if (!$this->sessionName) { if (!$this->sessionName) {
throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class)); throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class));
} }
@ -141,7 +141,7 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
*/ */
if (null === $cookie || isset($_COOKIE[$this->sessionName])) { if (null === $cookie || isset($_COOKIE[$this->sessionName])) {
if (\PHP_VERSION_ID < 70300) { if (\PHP_VERSION_ID < 70300) {
setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), filter_var(ini_get('session.cookie_secure'), \FILTER_VALIDATE_BOOLEAN), filter_var(ini_get('session.cookie_httponly'), \FILTER_VALIDATE_BOOLEAN)); setcookie($this->sessionName, '', 0, \ini_get('session.cookie_path'), \ini_get('session.cookie_domain'), filter_var(\ini_get('session.cookie_secure'), \FILTER_VALIDATE_BOOLEAN), filter_var(\ini_get('session.cookie_httponly'), \FILTER_VALIDATE_BOOLEAN));
} else { } else {
$params = session_get_cookie_params(); $params = session_get_cookie_params();
unset($params['lifetime']); unset($params['lifetime']);

View File

@ -77,7 +77,7 @@ class MemcachedSessionHandler extends AbstractSessionHandler
#[\ReturnTypeWillChange] #[\ReturnTypeWillChange]
public function updateTimestamp($sessionId, $data) public function updateTimestamp($sessionId, $data)
{ {
$this->memcached->touch($this->prefix.$sessionId, time() + (int) ($this->ttl ?? ini_get('session.gc_maxlifetime'))); $this->memcached->touch($this->prefix.$sessionId, time() + (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')));
return true; return true;
} }
@ -87,7 +87,7 @@ class MemcachedSessionHandler extends AbstractSessionHandler
*/ */
protected function doWrite(string $sessionId, string $data) protected function doWrite(string $sessionId, string $data)
{ {
return $this->memcached->set($this->prefix.$sessionId, $data, time() + (int) ($this->ttl ?? ini_get('session.gc_maxlifetime'))); return $this->memcached->set($this->prefix.$sessionId, $data, time() + (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')));
} }
/** /**

View File

@ -121,7 +121,7 @@ class MongoDbSessionHandler extends AbstractSessionHandler
*/ */
protected function doWrite(string $sessionId, string $data) protected function doWrite(string $sessionId, string $data)
{ {
$expiry = new UTCDateTime((time() + (int) ini_get('session.gc_maxlifetime')) * 1000); $expiry = new UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000);
$fields = [ $fields = [
$this->options['time_field'] => new UTCDateTime(), $this->options['time_field'] => new UTCDateTime(),
@ -144,7 +144,7 @@ class MongoDbSessionHandler extends AbstractSessionHandler
#[\ReturnTypeWillChange] #[\ReturnTypeWillChange]
public function updateTimestamp($sessionId, $data) public function updateTimestamp($sessionId, $data)
{ {
$expiry = new UTCDateTime((time() + (int) ini_get('session.gc_maxlifetime')) * 1000); $expiry = new UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000);
$this->getCollection()->updateOne( $this->getCollection()->updateOne(
[$this->options['id_field'] => $sessionId], [$this->options['id_field'] => $sessionId],

View File

@ -31,7 +31,7 @@ class NativeFileSessionHandler extends \SessionHandler
public function __construct(string $savePath = null) public function __construct(string $savePath = null)
{ {
if (null === $savePath) { if (null === $savePath) {
$savePath = ini_get('session.save_path'); $savePath = \ini_get('session.save_path');
} }
$baseDir = $savePath; $baseDir = $savePath;

View File

@ -344,7 +344,7 @@ class PdoSessionHandler extends AbstractSessionHandler
*/ */
protected function doWrite(string $sessionId, string $data) protected function doWrite(string $sessionId, string $data)
{ {
$maxlifetime = (int) ini_get('session.gc_maxlifetime'); $maxlifetime = (int) \ini_get('session.gc_maxlifetime');
try { try {
// We use a single MERGE SQL query when supported by the database. // We use a single MERGE SQL query when supported by the database.
@ -391,7 +391,7 @@ class PdoSessionHandler extends AbstractSessionHandler
#[\ReturnTypeWillChange] #[\ReturnTypeWillChange]
public function updateTimestamp($sessionId, $data) public function updateTimestamp($sessionId, $data)
{ {
$expiry = time() + (int) ini_get('session.gc_maxlifetime'); $expiry = time() + (int) \ini_get('session.gc_maxlifetime');
try { try {
$updateStmt = $this->pdo->prepare( $updateStmt = $this->pdo->prepare(
@ -687,7 +687,7 @@ class PdoSessionHandler extends AbstractSessionHandler
throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.'); throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.');
} }
if (!filter_var(ini_get('session.use_strict_mode'), \FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { if (!filter_var(\ini_get('session.use_strict_mode'), \FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
// In strict mode, session fixation is not possible: new sessions always start with a unique // In strict mode, session fixation is not possible: new sessions always start with a unique
// random id, so that concurrency is not possible and this code path can be skipped. // random id, so that concurrency is not possible and this code path can be skipped.
// Exclusive-reading of non-existent rows does not block, so we need to do an insert to block // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
@ -935,7 +935,7 @@ class PdoSessionHandler extends AbstractSessionHandler
protected function getConnection() protected function getConnection()
{ {
if (null === $this->pdo) { if (null === $this->pdo) {
$this->connect($this->dsn ?: ini_get('session.save_path')); $this->connect($this->dsn ?: \ini_get('session.save_path'));
} }
return $this->pdo; return $this->pdo;

View File

@ -79,7 +79,7 @@ class RedisSessionHandler extends AbstractSessionHandler
*/ */
protected function doWrite(string $sessionId, string $data): bool protected function doWrite(string $sessionId, string $data): bool
{ {
$result = $this->redis->setEx($this->prefix.$sessionId, (int) ($this->ttl ?? ini_get('session.gc_maxlifetime')), $data); $result = $this->redis->setEx($this->prefix.$sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')), $data);
return $result && !$result instanceof ErrorInterface; return $result && !$result instanceof ErrorInterface;
} }
@ -132,6 +132,6 @@ class RedisSessionHandler extends AbstractSessionHandler
#[\ReturnTypeWillChange] #[\ReturnTypeWillChange]
public function updateTimestamp($sessionId, $data) public function updateTimestamp($sessionId, $data)
{ {
return (bool) $this->redis->expire($this->prefix.$sessionId, (int) ($this->ttl ?? ini_get('session.gc_maxlifetime'))); return (bool) $this->redis->expire($this->prefix.$sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')));
} }
} }

View File

@ -30,6 +30,16 @@ class StrictSessionHandler extends AbstractSessionHandler
$this->handler = $handler; $this->handler = $handler;
} }
/**
* Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
*
* @internal
*/
public function isWrapper(): bool
{
return $this->handler instanceof \SessionHandler;
}
/** /**
* @return bool * @return bool
*/ */

View File

@ -162,6 +162,6 @@ class MetadataBag implements SessionBagInterface
{ {
$timeStamp = time(); $timeStamp = time();
$this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp;
$this->meta[self::LIFETIME] = $lifetime ?? (int) ini_get('session.cookie_lifetime'); $this->meta[self::LIFETIME] = $lifetime ?? (int) \ini_get('session.cookie_lifetime');
} }
} }

View File

@ -141,12 +141,42 @@ class NativeSessionStorage implements SessionStorageInterface
throw new \RuntimeException('Failed to start the session: already started by PHP.'); throw new \RuntimeException('Failed to start the session: already started by PHP.');
} }
if (filter_var(ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) { if (filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) {
throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line)); throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
} }
$sessionId = $_COOKIE[session_name()] ?? null; $sessionId = $_COOKIE[session_name()] ?? null;
if ($sessionId && $this->saveHandler instanceof AbstractProxy && 'files' === $this->saveHandler->getSaveHandlerName() && !preg_match('/^[a-zA-Z0-9,-]{22,}$/', $sessionId)) { /*
* Explanation of the session ID regular expression: `/^[a-zA-Z0-9,-]{22,250}$/`.
*
* ---------- Part 1
*
* The part `[a-zA-Z0-9,-]` is related to the PHP ini directive `session.sid_bits_per_character` defined as 6.
* See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character.
* Allowed values are integers such as:
* - 4 for range `a-f0-9`
* - 5 for range `a-v0-9`
* - 6 for range `a-zA-Z0-9,-`
*
* ---------- Part 2
*
* The part `{22,250}` is related to the PHP ini directive `session.sid_length`.
* See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length.
* Allowed values are integers between 22 and 256, but we use 250 for the max.
*
* Where does the 250 come from?
* - The length of Windows and Linux filenames is limited to 255 bytes. Then the max must not exceed 255.
* - The session filename prefix is `sess_`, a 5 bytes string. Then the max must not exceed 255 - 5 = 250.
*
* ---------- Conclusion
*
* The parts 1 and 2 prevent the warning below:
* `PHP Warning: SessionHandler::read(): Session ID is too long or contains illegal characters. Only the A-Z, a-z, 0-9, "-", and "," characters are allowed.`
*
* The part 2 prevents the warning below:
* `PHP Warning: SessionHandler::read(): open(filepath, O_RDWR) failed: No such file or directory (2).`
*/
if ($sessionId && $this->saveHandler instanceof AbstractProxy && 'files' === $this->saveHandler->getSaveHandlerName() && !preg_match('/^[a-zA-Z0-9,-]{22,250}$/', $sessionId)) {
// the session ID in the header is invalid, create a new one // the session ID in the header is invalid, create a new one
session_id(session_create_id()); session_id(session_create_id());
} }
@ -214,7 +244,7 @@ class NativeSessionStorage implements SessionStorageInterface
return false; return false;
} }
if (null !== $lifetime && $lifetime != ini_get('session.cookie_lifetime')) { if (null !== $lifetime && $lifetime != \ini_get('session.cookie_lifetime')) {
$this->save(); $this->save();
ini_set('session.cookie_lifetime', $lifetime); ini_set('session.cookie_lifetime', $lifetime);
$this->start(); $this->start();
@ -249,7 +279,7 @@ class NativeSessionStorage implements SessionStorageInterface
unset($_SESSION[$key]); unset($_SESSION[$key]);
} }
} }
if ([$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) { if ($_SESSION && [$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) {
unset($_SESSION[$key]); unset($_SESSION[$key]);
} }

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
/** /**
* @author Drak <drak@zikula.org> * @author Drak <drak@zikula.org>
*/ */
@ -22,7 +24,7 @@ class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterf
{ {
$this->handler = $handler; $this->handler = $handler;
$this->wrapper = $handler instanceof \SessionHandler; $this->wrapper = $handler instanceof \SessionHandler;
$this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user'; $this->saveHandlerName = $this->wrapper || ($handler instanceof StrictSessionHandler && $handler->isWrapper()) ? \ini_get('session.save_handler') : 'user';
} }
/** /**

View File

@ -24,8 +24,11 @@
"require-dev": { "require-dev": {
"predis/predis": "~1.0", "predis/predis": "~1.0",
"symfony/cache": "^4.4|^5.0|^6.0", "symfony/cache": "^4.4|^5.0|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4",
"symfony/mime": "^4.4|^5.0|^6.0", "symfony/mime": "^4.4|^5.0|^6.0",
"symfony/expression-language": "^4.4|^5.0|^6.0" "symfony/expression-language": "^4.4|^5.0|^6.0",
"symfony/rate-limiter": "^5.2|^6.0"
}, },
"suggest" : { "suggest" : {
"symfony/mime": "To use the file extension guesser" "symfony/mime": "To use the file extension guesser"

View File

@ -28,7 +28,7 @@ class ArgsStub extends EnumStub
$values = []; $values = [];
foreach ($args as $k => $v) { foreach ($args as $k => $v) {
$values[$k] = !is_scalar($v) && !$v instanceof Stub ? new CutStub($v) : $v; $values[$k] = !\is_scalar($v) && !$v instanceof Stub ? new CutStub($v) : $v;
} }
if (null === $params) { if (null === $params) {
parent::__construct($values, false); parent::__construct($values, false);

View File

@ -102,7 +102,7 @@ class IntlCaster
'SIGNIFICANT_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL), 'SIGNIFICANT_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL),
'MONETARY_GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL), 'MONETARY_GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL),
] ]
), ),
]; ];
return self::castError($c, $a); return self::castError($c, $a);

View File

@ -45,7 +45,7 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
public function __construct($output = null, string $charset = null, int $flags = 0) public function __construct($output = null, string $charset = null, int $flags = 0)
{ {
$this->flags = $flags; $this->flags = $flags;
$this->setCharset($charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8'); $this->setCharset($charset ?: \ini_get('php.output_encoding') ?: \ini_get('default_charset') ?: 'UTF-8');
$this->decimalPoint = \PHP_VERSION_ID >= 80000 ? '.' : localeconv()['decimal_point']; $this->decimalPoint = \PHP_VERSION_ID >= 80000 ? '.' : localeconv()['decimal_point'];
$this->setOutput($output ?: static::$defaultOutput); $this->setOutput($output ?: static::$defaultOutput);
if (!$output && \is_string(static::$defaultOutput)) { if (!$output && \is_string(static::$defaultOutput)) {

View File

@ -83,7 +83,7 @@ class CliDumper extends AbstractDumper
]); ]);
} }
$this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l'; $this->displayOptions['fileLinkFormat'] = \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l';
} }
/** /**

View File

@ -81,7 +81,7 @@ class HtmlDumper extends CliDumper
{ {
AbstractDumper::__construct($output, $charset, $flags); AbstractDumper::__construct($output, $charset, $flags);
$this->dumpId = 'sf-dump-'.mt_rand(); $this->dumpId = 'sf-dump-'.mt_rand();
$this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); $this->displayOptions['fileLinkFormat'] = \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
$this->styles = static::$themes['dark'] ?? self::$themes['dark']; $this->styles = static::$themes['dark'] ?? self::$themes['dark'];
} }
@ -882,7 +882,7 @@ EOHTML
} }
if ('const' === $style && isset($attr['value'])) { if ('const' === $style && isset($attr['value'])) {
$style .= sprintf(' title="%s"', esc(is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value']))); $style .= sprintf(' title="%s"', esc(\is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value'])));
} elseif ('public' === $style) { } elseif ('public' === $style) {
$style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property'); $style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property');
} elseif ('str' === $style && 1 < $attr['length']) { } elseif ('str' === $style && 1 < $attr['length']) {

View File

@ -4,4 +4,7 @@ composer.lock
.DS_Store .DS_Store
Thumbs.db Thumbs.db
/.idea /.idea
/.vscode /.vscode
/.settings
/.buildpath
/.project

View File

@ -58,10 +58,11 @@ if (!function_exists('abort')) {
if (!function_exists('app')) { if (!function_exists('app')) {
/** /**
* 快速获取容器中的实例 支持依赖注入 * 快速获取容器中的实例 支持依赖注入
* @param string $name 类名或标识 默认获取当前应用实例 * @template T
* @param array $args 参数 * @param string|class-string<T> $name 类名或标识 默认获取当前应用实例
* @param bool $newInstance 是否每次创建新的实例 * @param array $args 参数
* @return object|App * @param bool $newInstance 是否每次创建新的实例
* @return T|object|App
*/ */
function app(string $name = '', array $args = [], bool $newInstance = false) function app(string $name = '', array $args = [], bool $newInstance = false)
{ {

View File

@ -39,7 +39,7 @@ use think\initializer\RegisterService;
*/ */
class App extends Container class App extends Container
{ {
const VERSION = '6.0.12LTS'; const VERSION = '6.0.13LTS';
/** /**
* 应用调试模式 * 应用调试模式

View File

@ -108,11 +108,11 @@ class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, C
/** /**
* 获取容器中的对象实例 不存在则创建 * 获取容器中的对象实例 不存在则创建
* @access public * @template T
* @param string $abstract 类名或者标识 * @param string|class-string<T> $abstract 类名或者标识
* @param array|true $vars 变量 * @param array $vars 变量
* @param bool $newInstance 是否每次创建新的实例 * @param bool $newInstance 是否每次创建新的实例
* @return object * @return T|object
*/ */
public static function pull(string $abstract, array $vars = [], bool $newInstance = false) public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
{ {
@ -121,9 +121,9 @@ class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, C
/** /**
* 获取容器中的对象实例 * 获取容器中的对象实例
* @access public * @template T
* @param string $abstract 类名或者标识 * @param string|class-string<T> $abstract 类名或者标识
* @return object * @return T|object
*/ */
public function get($abstract) public function get($abstract)
{ {
@ -232,11 +232,11 @@ class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, C
/** /**
* 创建类的实例 已经存在则直接获取 * 创建类的实例 已经存在则直接获取
* @access public * @template T
* @param string $abstract 类名或者标识 * @param string|class-string<T> $abstract 类名或者标识
* @param array $vars 变量 * @param array $vars 变量
* @param bool $newInstance 是否每次创建新的实例 * @param bool $newInstance 是否每次创建新的实例
* @return mixed * @return T|object
*/ */
public function make(string $abstract, array $vars = [], bool $newInstance = false) public function make(string $abstract, array $vars = [], bool $newInstance = false)
{ {

View File

@ -129,6 +129,8 @@ class Lang
return; return;
} }
$this->setLangSet($langset);
// 加载系统语言包 // 加载系统语言包
$this->load([ $this->load([
$this->app->getThinkPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.php', $this->app->getThinkPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.php',

View File

@ -1237,7 +1237,7 @@ class Request implements ArrayAccess
* @access public * @access public
* @param string $name header名称 * @param string $name header名称
* @param string $default 默认值 * @param string $default 默认值
* @return string|array * @return string|array|null
*/ */
public function header(string $name = '', string $default = null) public function header(string $name = '', string $default = null)
{ {
@ -1416,6 +1416,10 @@ class Request implements ArrayAccess
foreach ($filters as $filter) { foreach ($filters as $filter) {
if (is_callable($filter)) { if (is_callable($filter)) {
// 调用函数或者方法过滤 // 调用函数或者方法过滤
if (is_null($value)) {
continue;
}
$value = call_user_func($filter, $value); $value = call_user_func($filter, $value);
} elseif (is_scalar($value)) { } elseif (is_scalar($value)) {
if (is_string($filter) && false !== strpos($filter, '/')) { if (is_string($filter) && false !== strpos($filter, '/')) {
@ -1490,7 +1494,7 @@ class Request implements ArrayAccess
if (is_int($key)) { if (is_int($key)) {
$default = null; $default = null;
$key = $val; $key = $val;
if (!isset($data[$key])) { if (!key_exists($key, $data)) {
continue; continue;
} }
} else { } else {

View File

@ -167,6 +167,8 @@ class Route
} }
$this->config = array_merge($this->config, $this->app->config->get('route')); $this->config = array_merge($this->config, $this->app->config->get('route'));
$this->init();
} }
protected function init() protected function init()
@ -630,6 +632,18 @@ class Route
return $this->rule($rule, $route, 'PATCH'); return $this->rule($rule, $route, 'PATCH');
} }
/**
* 注册HEAD路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @return RuleItem
*/
public function head(string $rule, $route): RuleItem
{
return $this->rule($rule, $route, 'HEAD');
}
/** /**
* 注册OPTIONS路由 * 注册OPTIONS路由
* @access public * @access public
@ -752,7 +766,6 @@ class Route
{ {
$this->request = $request; $this->request = $request;
$this->host = $this->request->host(true); $this->host = $this->request->host(true);
$this->init();
if ($withRoute) { if ($withRoute) {
//加载路由 //加载路由

View File

@ -153,7 +153,9 @@ abstract class Driver implements CacheInterface, CacheHandlerInterface
public function remember(string $name, $value, $expire = null) public function remember(string $name, $value, $expire = null)
{ {
if ($this->has($name)) { if ($this->has($name)) {
return $this->get($name); if (($hit = $this->get($name)) !== null) {
return $hit;
}
} }
$time = time(); $time = time();

View File

@ -214,12 +214,12 @@ class Console
/** /**
* 获取终端模式 * 获取终端模式
* @return string <width>x<height> null * @return string <width>x<height>
*/ */
private function getMode() private function getMode()
{ {
if (!function_exists('proc_open')) { if (!function_exists('proc_open')) {
return; return '';
} }
$descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
@ -234,7 +234,8 @@ class Console
return $matches[2] . 'x' . $matches[1]; return $matches[2] . 'x' . $matches[1];
} }
} }
return;
return '';
} }
private function stringWidth(string $string): int private function stringWidth(string $string): int

View File

@ -56,6 +56,7 @@ use think\route\Url as UrlBuild;
* @method static RuleItem put(string $rule, mixed $route) 注册PUT路由 * @method static RuleItem put(string $rule, mixed $route) 注册PUT路由
* @method static RuleItem delete(string $rule, mixed $route) 注册DELETE路由 * @method static RuleItem delete(string $rule, mixed $route) 注册DELETE路由
* @method static RuleItem patch(string $rule, mixed $route) 注册PATCH路由 * @method static RuleItem patch(string $rule, mixed $route) 注册PATCH路由
* @method static RuleItem head(string $rule, mixed $route) 注册HEAD路由
* @method static RuleItem options(string $rule, mixed $route) 注册OPTIONS路由 * @method static RuleItem options(string $rule, mixed $route) 注册OPTIONS路由
* @method static Resource resource(string $rule, string $route) 注册资源路由 * @method static Resource resource(string $rule, string $route) 注册资源路由
* @method static RuleItem view(string $rule, string $template = '', array $vars = []) 注册视图路由 * @method static RuleItem view(string $rule, string $template = '', array $vars = []) 注册视图路由

View File

@ -42,8 +42,15 @@ class Local extends Driver
); );
} }
/**
* 获取文件访问地址
* @param string $path 文件路径
* @return string
*/
public function url(string $path): string public function url(string $path): string
{ {
$path = str_replace('\\', '/', $path);
if (isset($this->config['url'])) { if (isset($this->config['url'])) {
return $this->concatPathToUrl($this->config['url'], $path); return $this->concatPathToUrl($this->config['url'], $path);
} }

View File

@ -74,9 +74,9 @@ class Redirect extends Response
* @access public * @access public
* @return $this * @return $this
*/ */
public function remember() public function remember($complete = false)
{ {
$this->session->set('redirect_url', $this->request->url()); $this->session->set('redirect_url', $this->request->url($complete));
return $this; return $this;
} }

View File

@ -96,7 +96,7 @@ abstract class Dispatch
if ($data instanceof Response) { if ($data instanceof Response) {
$response = $data; $response = $data;
} elseif ($data instanceof ResponseInterface) { } elseif ($data instanceof ResponseInterface) {
$response = Response::create($data->getBody()->getContents(), 'html', $data->getStatusCode()); $response = Response::create((string) $data->getBody(), 'html', $data->getStatusCode());
foreach ($data->getHeaders() as $header => $values) { foreach ($data->getHeaders() as $header => $values) {
$response->header([$header => implode(", ", $values)]); $response->header([$header => implode(", ", $values)]);

View File

@ -492,7 +492,7 @@ if (!function_exists('echo_value')) {
} }
})(); })();
$.getScript('//cdn.bootcss.com/prettify/r298/prettify.min.js', function(){ $.getScript('//cdn.bootcdn.net/ajax/libs/prettify/r298/prettify.min.js', function(){
prettyPrint(); prettyPrint();
}); });
})(); })();

View File

@ -16,8 +16,8 @@
"php": ">=7.1.0", "php": ">=7.1.0",
"ext-json": "*", "ext-json": "*",
"ext-pdo": "*", "ext-pdo": "*",
"psr/simple-cache": "^1.0", "psr/simple-cache": "^1.0|^2.0",
"psr/log": "~1.0", "psr/log": "^1.0|^2.0",
"topthink/think-helper":"^3.1" "topthink/think-helper":"^3.1"
}, },
"require-dev": { "require-dev": {

View File

@ -628,7 +628,7 @@ abstract class BaseQuery
if (!isset($total) && !$simple) { if (!isset($total) && !$simple) {
$options = $this->getOptions(); $options = $this->getOptions();
unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); unset($this->options['order'], $this->options['cache'], $this->options['limit'], $this->options['page'], $this->options['field']);
$bind = $this->bind; $bind = $this->bind;
$total = $this->count(); $total = $this->count();
@ -705,7 +705,7 @@ abstract class BaseQuery
->limit(1) ->limit(1)
->find(); ->find();
$result = $data[$key]; $result = $data[$key] ?? 0;
if (is_numeric($result)) { if (is_numeric($result)) {
$lastId = 'asc' == $sort ? ($result - 1) + ($page - 1) * $listRows : ($result + 1) - ($page - 1) * $listRows; $lastId = 'asc' == $sort ? ($result - 1) + ($page - 1) * $listRows : ($result + 1) - ($page - 1) * $listRows;

View File

@ -1274,7 +1274,7 @@ abstract class PDOConnection extends Connection
$type = is_array($val) ? $val[1] : PDO::PARAM_STR; $type = is_array($val) ? $val[1] : PDO::PARAM_STR;
if (self::PARAM_FLOAT == $type || PDO::PARAM_STR == $type) { if (self::PARAM_FLOAT == $type || PDO::PARAM_STR == $type) {
$value = '\'' . addcslashes($value, "'") . '\''; $value = '\'' . addslashes($value) . '\'';
} elseif (PDO::PARAM_INT == $type && '' === $value) { } elseif (PDO::PARAM_INT == $type && '' === $value) {
$value = '0'; $value = '0';
} }

View File

@ -12,6 +12,8 @@ namespace think\db\builder;
use think\db\Builder; use think\db\Builder;
use think\db\Query; use think\db\Query;
use think\db\exception\DbException as Exception;
use think\db\Raw;
/** /**
* Oracle数据库驱动 * Oracle数据库驱动
@ -64,19 +66,51 @@ class Oracle extends Builder
/** /**
* 字段和表名处理 * 字段和表名处理
* @access public * @access public
* @param Query $query 查询对象 * @param Query $query 查询对象
* @param string $key * @param string $key
* @param string $strict * @param bool $strict
* @return string * @return string
* @throws Exception
*/ */
public function parseKey(Query $query, $key, bool $strict = false): 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); $key = trim($key);
if (strpos($key, '->') && false === strpos($key, '(')) { if (strpos($key, '->') && false === strpos($key, '(')) {
// JSON字段支持 // JSON字段支持
[$field, $name] = explode($key, '->'); [$field, $name] = explode($key, '->');
$key = $field . '."' . $name . '"'; $key = $field . '."' . $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)) {
$key = '"' . $table . '".' . $key;
} }
return $key; return $key;

View File

@ -12,6 +12,7 @@ declare (strict_types = 1);
namespace think\db\concern; namespace think\db\concern;
use think\db\exception\DbException;
use think\db\Raw; use think\db\Raw;
/** /**
@ -42,6 +43,11 @@ trait AggregateQuery
{ {
if (!empty($this->options['group'])) { if (!empty($this->options['group'])) {
// 支持GROUP // 支持GROUP
if (!preg_match('/^[\w\.\*]+$/', $field)) {
throw new DbException('not support data:' . $field);
}
$options = $this->getOptions(); $options = $this->getOptions();
$subSql = $this->options($options) $subSql = $this->options($options)
->field('count(' . $field . ') AS think_count') ->field('count(' . $field . ') AS think_count')

View File

@ -51,7 +51,7 @@ class Oracle extends PDOConnection
public function getFields(string $tableName): array public function getFields(string $tableName): array
{ {
[$tableName] = explode(' ', $tableName); [$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 (+)"; $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 = '" . $tableName . "' ) b where table_name = '" . $tableName . "' and a.column_name = b.column_name (+)";
$pdo = $this->getPDOStatement($sql); $pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC); $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
@ -98,16 +98,18 @@ class Oracle extends PDOConnection
/** /**
* 获取最近插入的ID * 获取最近插入的ID
* @access public * @access public
* @param BaseQuery $query 查询对象 * @param BaseQuery $query 查询对象
* @param string $sequence 自增序列名 * @param string|null $sequence 自增序列名
* @return mixed * @return mixed
*/ */
public function getLastInsID(BaseQuery $query, string $sequence = null) public function getLastInsID(BaseQuery $query, string $sequence = null)
{ {
$pdo = $this->linkID->query("select {$sequence}.currval as id from dual"); if(!is_null($sequence)) {
$result = $pdo->fetchColumn(); $pdo = $this->linkID->query("select {$sequence}.currval as id from dual");
$result = $pdo->fetchColumn();
}
return $result; return $result ?? null;
} }
protected function supportSavepoint(): bool protected function supportSavepoint(): bool

View File

@ -44,6 +44,10 @@ class Sqlsrv extends PDOConnection
$dsn .= ',' . $config['hostport']; $dsn .= ',' . $config['hostport'];
} }
if (!empty($config['trust_server_certificate'])) {
$dsn .= ';TrustServerCertificate=' . $config['trust_server_certificate'];
}
return $dsn; return $dsn;
} }

View File

@ -81,12 +81,13 @@ class Collection extends BaseCollection
* 设置需要隐藏的输出属性 * 设置需要隐藏的输出属性
* @access public * @access public
* @param array $hidden 属性列表 * @param array $hidden 属性列表
* @param bool $merge 是否合并
* @return $this * @return $this
*/ */
public function hidden(array $hidden) public function hidden(array $hidden, bool $merge = false)
{ {
$this->each(function (Model $model) use ($hidden) { $this->each(function (Model $model) use ($hidden, $merge) {
$model->hidden($hidden); $model->hidden($hidden, $merge);
}); });
return $this; return $this;
@ -96,12 +97,13 @@ class Collection extends BaseCollection
* 设置需要输出的属性 * 设置需要输出的属性
* @access public * @access public
* @param array $visible * @param array $visible
* @param bool $merge 是否合并
* @return $this * @return $this
*/ */
public function visible(array $visible) public function visible(array $visible, bool $merge = false)
{ {
$this->each(function (Model $model) use ($visible) { $this->each(function (Model $model) use ($visible, $merge) {
$model->visible($visible); $model->visible($visible, $merge);
}); });
return $this; return $this;
@ -111,12 +113,13 @@ class Collection extends BaseCollection
* 设置需要追加的输出属性 * 设置需要追加的输出属性
* @access public * @access public
* @param array $append 属性列表 * @param array $append 属性列表
* @param bool $merge 是否合并
* @return $this * @return $this
*/ */
public function append(array $append) public function append(array $append, bool $merge = false)
{ {
$this->each(function (Model $model) use ($append) { $this->each(function (Model $model) use ($append, $merge) {
$model->append($append); $model->append($append, $merge);
}); });
return $this; return $this;

View File

@ -35,9 +35,9 @@ class Pivot extends Model
/** /**
* 架构函数 * 架构函数
* @access public * @access public
* @param array $data 数据 * @param array $data 数据
* @param Model $parent 上级模型 * @param Model|null $parent 上级模型
* @param string $table 中间数据表名 * @param string $table 中间数据表名
*/ */
public function __construct(array $data = [], Model $parent = null, string $table = '') public function __construct(array $data = [], Model $parent = null, string $table = '')
{ {
@ -50,4 +50,21 @@ class Pivot extends Model
parent::__construct($data); parent::__construct($data);
} }
/**
* 创建新的模型实例
* @access public
* @param array $data 数据
* @param mixed $where 更新条件
* @param array $options 参数
* @return Model
*/
public function newInstance(array $data = [], $where = null, array $options = []): Model
{
$model = parent::newInstance($data, $where, $options);
$model->parent = $this->parent;
$model->name = $this->name;
return $model;
}
} }

View File

@ -82,11 +82,16 @@ trait Conversion
* 设置需要附加的输出属性 * 设置需要附加的输出属性
* @access public * @access public
* @param array $append 属性列表 * @param array $append 属性列表
* @param bool $merge 是否合并
* @return $this * @return $this
*/ */
public function append(array $append = []) public function append(array $append = [], bool $merge = false)
{ {
$this->append = $append; if ($merge) {
$this->append = array_merge($this->append, $append);
} else {
$this->append = $append;
}
return $this; return $this;
} }
@ -147,11 +152,16 @@ trait Conversion
* 设置需要隐藏的输出属性 * 设置需要隐藏的输出属性
* @access public * @access public
* @param array $hidden 属性列表 * @param array $hidden 属性列表
* @param bool $merge 是否合并
* @return $this * @return $this
*/ */
public function hidden(array $hidden = []) public function hidden(array $hidden = [], bool $merge = false)
{ {
$this->hidden = $hidden; if ($merge) {
$this->hidden = array_merge($this->hidden, $hidden);
} else {
$this->hidden = $hidden;
}
return $this; return $this;
} }
@ -160,11 +170,16 @@ trait Conversion
* 设置需要输出的属性 * 设置需要输出的属性
* @access public * @access public
* @param array $visible * @param array $visible
* @param bool $merge 是否合并
* @return $this * @return $this
*/ */
public function visible(array $visible = []) public function visible(array $visible = [], bool $merge = false)
{ {
$this->visible = $visible; if ($merge) {
$this->visible = array_merge($this->visible, $visible);
} else {
$this->visible = $visible;
}
return $this; return $this;
} }

View File

@ -241,6 +241,7 @@ class BelongsTo extends OneToOne
if (!empty($this->bindAttr)) { if (!empty($this->bindAttr)) {
// 绑定关联属性 // 绑定关联属性
$this->bindAttr($result, $relationModel); $this->bindAttr($result, $relationModel);
$result->hidden([$relation]);
} }
} }
} }
@ -282,6 +283,7 @@ class BelongsTo extends OneToOne
if (!empty($this->bindAttr)) { if (!empty($this->bindAttr)) {
// 绑定关联属性 // 绑定关联属性
$this->bindAttr($result, $relationModel); $this->bindAttr($result, $relationModel);
$result->hidden([$relation]);
} }
} }

View File

@ -157,7 +157,12 @@ class BelongsToMany extends Relation
} }
} }
$result->setRelation($this->pivotDataName, $this->newPivot($pivot)); $pivotData = $this->pivot->newInstance($pivot, [
[$this->localKey, '=', $this->parent->getKey(), null],
[$this->foreignKey, '=', $result->getKey(), null],
]);
$result->setRelation($this->pivotDataName, $pivotData);
return $pivot; return $pivot;
} }

View File

@ -240,6 +240,7 @@ class HasOne extends OneToOne
if (!empty($this->bindAttr)) { if (!empty($this->bindAttr)) {
// 绑定关联属性 // 绑定关联属性
$this->bindAttr($result, $relationModel); $this->bindAttr($result, $relationModel);
$result->hidden([$relation]);
} }
} }
} }
@ -281,6 +282,7 @@ class HasOne extends OneToOne
if (!empty($this->bindAttr)) { if (!empty($this->bindAttr)) {
// 绑定关联属性 // 绑定关联属性
$this->bindAttr($result, $relationModel); $this->bindAttr($result, $relationModel);
$result->hidden([$relation]);
} }
} }

View File

@ -30,6 +30,7 @@ class MorphMany extends Relation
* @var string * @var string
*/ */
protected $morphKey; protected $morphKey;
/** /**
* 多态字段名 * 多态字段名
* @var string * @var string
@ -331,6 +332,33 @@ class MorphMany extends Relation
return empty($result) ? false : $result; return empty($result) ? false : $result;
} }
/**
* 获取多态关联外键
* @return string
*/
public function getMorphKey()
{
return $this->morphKey;
}
/**
* 获取多态字段名
* @return string
*/
public function getMorphType()
{
return $this->morphType;
}
/**
* 获取多态类型
* @return string
*/
public function getType()
{
return $this->type;
}
/** /**
* 执行基础查询(仅执行一次) * 执行基础查询(仅执行一次)
* @access protected * @access protected

View File

@ -208,6 +208,9 @@ class MorphTo extends Relation
// 多态类型映射 // 多态类型映射
$model = $this->parseModel($key); $model = $this->parseModel($key);
$obj = new $model; $obj = new $model;
if (!\is_null($closure)) {
$obj = $closure($obj);
}
$pk = $obj->getPk(); $pk = $obj->getPk();
$list = $obj->with($subRelation) $list = $obj->with($subRelation)
->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)

View File

@ -24,6 +24,13 @@ use think\model\Pivot;
class MorphToMany extends BelongsToMany class MorphToMany extends BelongsToMany
{ {
/**
* 多态关系的模型名映射别名的数组
*
* @var array
*/
protected static $morphMap = [];
/** /**
* 多态字段名 * 多态字段名
* @var string * @var string
@ -58,6 +65,9 @@ class MorphToMany extends BelongsToMany
$this->morphType = $morphType; $this->morphType = $morphType;
$this->inverse = $inverse; $this->inverse = $inverse;
$this->morphClass = $inverse ? $model : get_class($parent); $this->morphClass = $inverse ? $model : get_class($parent);
if (isset(static::$morphMap[$this->morphClass])) {
$this->morphClass = static::$morphMap[$this->morphClass];
}
$foreignKey = $inverse ? $morphKey : $localKey; $foreignKey = $inverse ? $morphKey : $localKey;
$localKey = $inverse ? $localKey : $morphKey; $localKey = $inverse ? $localKey : $morphKey;
@ -460,4 +470,21 @@ class MorphToMany extends BelongsToMany
} }
} }
/**
* 设置或获取多态关系的模型名映射别名的数组
*
* @param array|null $map
* @param bool $merge
* @return array
*/
public static function morphMap(array $map = null, $merge = true): array
{
if (is_array($map)) {
static::$morphMap = $merge && static::$morphMap
? $map + static::$morphMap : $map;
}
return static::$morphMap;
}
} }

View File

@ -186,18 +186,30 @@ abstract class OneToOne extends Relation
* @return Model|false * @return Model|false
*/ */
public function save($data, bool $replace = true) 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) { if ($data instanceof Model) {
$data = $data->getData(); $data = $data->getData();
} }
$model = new $this->model;
// 保存关联表数据 // 保存关联表数据
$data[$this->foreignKey] = $this->parent->{$this->localKey}; $data[$this->foreignKey] = $this->parent->{$this->localKey};
return $model->replace($replace)->save($data) ? $model : false; return new $this->model($data);
} }
/** /**
* 绑定关联表的属性到父模型属性 * 绑定关联表的属性到父模型属性
* @access public * @access public