教程:Hyperf
一 原理
使用Hyperf\Database\Model\Model::__callStatic()调用Model::__call()。
Model::__call()使用Model::newQuery()和传递的方法名,返回\Hyperf\Database\Model\Builder类。并使用Hyperf\Database\Model\Builder::withGlobalScope()注册一个新的全局作用域。
根据文档调用Builder::withCasts($casts),实际上是使用Hyperf\Database\Model\Concerns\HasAttributes::mergeCasts($casts)合并casts数组。
最后调用Builder::get()返回\Hyperf\Database\Model\Collection类。获取Collection类后,根据源码若是调用该类属性仅限于以下内容。
#Hyperf\Utils\Collection
protected static $proxies= ['average','avg','contains','each',
'every','filter','first','flatMap','groupBy','keyBy','map',
'max','min','partition','reject','sortBy','sortByDesc',
'sum','unique',];
可以使用Hyperf\Utils\Collection::first()、Hyperf\Utils\Collection::map()或相关函数获取结果。详见Hyperf\Utils\Collection。
获取的结果应该是对应model类的对象或对象数组,接下来就可以使用model类的对应方法。
若使用casts数组,可调用Hyperf\Database\Model\Concerns\HasAttributes::attributesToArray()。
二 测试
2.1 测试1
#App\Controller\TestController
public function testmodel3() {
$info = Article::from("articles as a")->select([
'a.title',
'content' => User::selectRaw('MAX(created_at)')->whereColumn('a.user_id', 'users.id'),
])->withCasts([
'created_at' => 'date',
])->get();
var_dump($info);
}
测试结果
[ERROR] stripos() expects parameter 1 to be string, object given[286] in /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Query/Grammars/Grammar.php
[ERROR] #0 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Query/Grammars/Grammar.php(286): stripos()
#1 [internal function]: Hyperf\Database\Query\Grammars\Grammar->wrap()
#2 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Grammar.php(85): array_map()
#3 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Query/Grammars/Grammar.php(366): Hyperf\Database\Grammar->columnize()
#4 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Query/Grammars/Grammar.php(324): Hyperf\Database\Query\Grammars\Grammar->compileColumns()
#5 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Query/Grammars/Grammar.php(73): Hyperf\Database\Query\Grammars\Grammar->compileComponents()
#6 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Query/Grammars/MySqlGrammar.php(57): Hyperf\Database\Query\Grammars\Grammar->compileSelect()
#7 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Query/Builder.php(1748): Hyperf\Database\Query\Grammars\MySqlGrammar->compileSelect()
#8 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Query/Builder.php(2590): Hyperf\Database\Query\Builder->toSql()
#9 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Query/Builder.php(1780): Hyperf\Database\Query\Builder->runSelect()
#10 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Query/Builder.php(2746): Hyperf\Database\Query\Builder->Hyperf\Database\Query\{closure}()
#11 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Query/Builder.php(1781): Hyperf\Database\Query\Builder->onceWithColumns()
#12 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Model/Builder.php(560): Hyperf\Database\Query\Builder->get()
#13 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Model/Builder.php(545): Hyperf\Database\Model\Builder->getModels()
#14 /wj/hyperf/hyperfpro2/runtime/container/proxy/App_Controller_TestController.proxy.php(432): Hyperf\Database\Model\Builder->get()
#15 /wj/hyperf/hyperfpro2/vendor/hyperf/http-server/src/CoreMiddleware.php(161): App\Controller\TestController->testmodel3()
#16 /wj/hyperf/hyperfpro2/vendor/hyperf/http-server/src/CoreMiddleware.php(113): Hyperf\HttpServer\CoreMiddleware->handleFound()
#17 /wj/hyperf/hyperfpro2/vendor/hyperf/dispatcher/src/AbstractRequestHandler.php(64): Hyperf\HttpServer\CoreMiddleware->process()
#18 /wj/hyperf/hyperfpro2/vendor/hyperf/dispatcher/src/HttpRequestHandler.php(26): Hyperf\Dispatcher\AbstractRequestHandler->handleRequest()
#19 /wj/hyperf/hyperfpro2/vendor/hyperf/session/src/Middleware/SessionMiddleware.php(58): Hyperf\Dispatcher\HttpRequestHandler->handle()
#20 /wj/hyperf/hyperfpro2/vendor/hyperf/dispatcher/src/AbstractRequestHandler.php(64): Hyperf\Session\Middleware\SessionMiddleware->process()
#21 /wj/hyperf/hyperfpro2/vendor/hyperf/dispatcher/src/HttpRequestHandler.php(26): Hyperf\Dispatcher\AbstractRequestHandler->handleRequest()
#22 /wj/hyperf/hyperfpro2/vendor/hyperf/validation/src/Middleware/ValidationMiddleware.php(83): Hyperf\Dispatcher\HttpRequestHandler->handle()
#23 /wj/hyperf/hyperfpro2/vendor/hyperf/dispatcher/src/AbstractRequestHandler.php(64): Hyperf\Validation\Middleware\ValidationMiddleware->process()
#24 /wj/hyperf/hyperfpro2/vendor/hyperf/dispatcher/src/HttpRequestHandler.php(26): Hyperf\Dispatcher\AbstractRequestHandler->handleRequest()
#25 /wj/hyperf/hyperfpro2/vendor/hyperf/dispatcher/src/HttpDispatcher.php(40): Hyperf\Dispatcher\HttpRequestHandler->handle()
#26 /wj/hyperf/hyperfpro2/vendor/hyperf/http-server/src/Server.php(117): Hyperf\Dispatcher\HttpDispatcher->dispatch()
#27 {main}
经过调试
#Hyperf\Database\Grammar
public function columnize(array $columns) {
//测试用
foreach ($columns as $key => $value) {
if (is_object($value)) {
var_dump(get_class($value));
} else {
var_dump($value);
}
}
//测试用 end
return implode(', ', array_map([$this, 'wrap'], $columns));
}
string(7) "a.title"
string(29) "Hyperf\Database\Model\Builder"
所以select()方法中,参数content值是Hyperf\Database\Model\Builder对象,所以报错。
可见对于当前版本,文档上的内容不可用。
修改后内容如下
#App\Controller\TestController
public function testmodel3() {
$info = Article::from("articles as a")->select([
'a.title',
'content' => User::selectRaw('MAX(created_at)')->whereColumn('a.user_id', 'users.id')->toSql(),
])->withCasts([
'created_at' => 'date',
])->get();
var_dump($info);
}
测试结果
[ERROR] SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '.```deleted_at`` is null` from `articles` as `a` where `articles`.`deleted_at` i' at line 1 (SQL: select `a`.`title`, `select MAX(created_at) from ``userinfo`` where ``a```.```user_id`` = ``users```.```id`` and ``userinfo```.```deleted_at`` is null` from `articles` as `a` where `articles`.`deleted_at` is null)[1088] in /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Connection.php
[ERROR] #0 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Connection.php(1045): Hyperf\Database\Connection->runQueryCallback()
#1 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Connection.php(289): Hyperf\Database\Connection->run()
#2 /wj/hyperf/hyperfpro2/vendor/hyperf/db-connection/src/Connection.php(68): Hyperf\Database\Connection->select()
#3 /wj/hyperf/hyperfpro2/vendor/hyperf/db-connection/src/Traits/DbConnection.php(41): Hyperf\DbConnection\Connection->__call()
#4 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Query/Builder.php(2590): Hyperf\DbConnection\Connection->select()
#5 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Query/Builder.php(1780): Hyperf\Database\Query\Builder->runSelect()
#6 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Query/Builder.php(2746): Hyperf\Database\Query\Builder->Hyperf\Database\Query\{closure}()
#7 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Query/Builder.php(1781): Hyperf\Database\Query\Builder->onceWithColumns()
#8 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Model/Builder.php(560): Hyperf\Database\Query\Builder->get()
#9 /wj/hyperf/hyperfpro2/vendor/hyperf/database/src/Model/Builder.php(545): Hyperf\Database\Model\Builder->getModels()
#10 /wj/hyperf/hyperfpro2/runtime/container/proxy/App_Controller_TestController.proxy.php(432): Hyperf\Database\Model\Builder->get()
#11 /wj/hyperf/hyperfpro2/vendor/hyperf/http-server/src/CoreMiddleware.php(161): App\Controller\TestController->testmodel3()
#12 /wj/hyperf/hyperfpro2/vendor/hyperf/http-server/src/CoreMiddleware.php(113): Hyperf\HttpServer\CoreMiddleware->handleFound()
#13 /wj/hyperf/hyperfpro2/vendor/hyperf/dispatcher/src/AbstractRequestHandler.php(64): Hyperf\HttpServer\CoreMiddleware->process()
#14 /wj/hyperf/hyperfpro2/vendor/hyperf/dispatcher/src/HttpRequestHandler.php(26): Hyperf\Dispatcher\AbstractRequestHandler->handleRequest()
#15 /wj/hyperf/hyperfpro2/vendor/hyperf/session/src/Middleware/SessionMiddleware.php(58): Hyperf\Dispatcher\HttpRequestHandler->handle()
#16 /wj/hyperf/hyperfpro2/vendor/hyperf/dispatcher/src/AbstractRequestHandler.php(64): Hyperf\Session\Middleware\SessionMiddleware->process()
#17 /wj/hyperf/hyperfpro2/vendor/hyperf/dispatcher/src/HttpRequestHandler.php(26): Hyperf\Dispatcher\AbstractRequestHandler->handleRequest()
#18 /wj/hyperf/hyperfpro2/vendor/hyperf/validation/src/Middleware/ValidationMiddleware.php(83): Hyperf\Dispatcher\HttpRequestHandler->handle()
#19 /wj/hyperf/hyperfpro2/vendor/hyperf/dispatcher/src/AbstractRequestHandler.php(64): Hyperf\Validation\Middleware\ValidationMiddleware->process()
#20 /wj/hyperf/hyperfpro2/vendor/hyperf/dispatcher/src/HttpRequestHandler.php(26): Hyperf\Dispatcher\AbstractRequestHandler->handleRequest()
#21 /wj/hyperf/hyperfpro2/vendor/hyperf/dispatcher/src/HttpDispatcher.php(40): Hyperf\Dispatcher\HttpRequestHandler->handle()
#22 /wj/hyperf/hyperfpro2/vendor/hyperf/http-server/src/Server.php(117): Hyperf\Dispatcher\HttpDispatcher->dispatch()
#23 {main}
调试内容
string(7) "a.title"
string(109) "select MAX(created_at) from `userinfo` where `a`.`user_id` = `users`.`id` and `userinfo`.`deleted_at` is null"
从报错中可见两个错误。
- 字符串拼接中查询userinfo的sql作为查询字段,运行肯定要报错。
- article表重命名后,其查询字段“where `articles`.`deleted_at`”,应为“ `a`.`deleted_at`”,但是并没有改变,肯定运行不通。
所以join表肯定不能按照文档上的内容写,可能是本地的版本不适用。
2.2 测试2
#App\Controller\TestController
public function testmodel3() {
$info = Article::select([
'articles.title',
'articles.created_at',
])->withCasts([
'created_at' => 'date:Y-m-d',
])->get();
var_dump(get_class($info), get_class($info->first()), $info->first()->attributesToArray());
}
测试结果
string(32) "Hyperf\Database\Model\Collection"
string(18) "App1\Model\Article"
array(2) {
["title"]=>
string(5) "test1"
["created_at"]=>
string(10) "2024-01-13"
}
2.3 测试3
#App\Controller\TestController
public function testmodel3() {
$info = Article::select([
'articles.title',
'articles.created_at',
])->join('userinfo', 'user_id', 'userinfo.id')->withCasts([
'created_at' => 'String',
])->get();
var_dump(get_class($info), get_class($info->first()), $info->first()->created_at);
}
测试结果
string(32) "Hyperf\Database\Model\Collection"
string(18) "App1\Model\Article"
string(19) "2024-01-13 10:05:51"
三 源码
#Hyperf\Database\Model\Model
use Hyperf\Database\Query\Builder as QueryBuilder;
public static function __callStatic($method, $parameters) {
return (new static())->{$method}(...$parameters);
}
public function __call($method, $parameters) {
if (in_array($method, ['increment', 'decrement'])) {
return $this->{$method}(...$parameters);
}
return call([$this->newQuery(), $method], $parameters);
}
public function newQuery() {
return $this->registerGlobalScopes($this->newQueryWithoutScopes());
}
public function newQueryWithoutScopes() {
return $this->newModelQuery()->with($this->with)->withCount($this->withCount);
}
public function newModelQuery() {
return $this->newModelBuilder($this->newBaseQueryBuilder())->setModel($this);
}
protected function newBaseQueryBuilder() {
$connection = $this->getConnection();
return new QueryBuilder($connection, $connection->getQueryGrammar(), $connection->getPostProcessor());
}
public function newModelBuilder($query) {
return new Builder($query);
}
#Hyperf\Database\Model\Builder
public function __construct(QueryBuilder $query) {
$this->query = $query;
}
public function __call($method, $parameters) {
if ($method === 'macro') {
$this->localMacros[$parameters[0]] = $parameters[1];
return;
}
if (isset($this->localMacros[$method])) {
array_unshift($parameters, $this);
return $this->localMacros[$method](...$parameters);
}
if (isset(static::$macros[$method])) {
if (static::$macros[$method] instanceof Closure) {
return call_user_func_array(static::$macros[$method]->bindTo($this, static::class), $parameters);
}
return call_user_func_array(static::$macros[$method], $parameters);
}
if (isset($this->model) && method_exists($this->model, $scope = 'scope' . ucfirst($method))) {
return $this->callScope([$this->model, $scope], $parameters);
}
if (in_array($method, $this->passthru)) {
return $this->toBase()->{$method}(...$parameters);
}
call([$this->query, $method], $parameters);
return $this;
}
#Hyperf\Database\Query\Builder
public function __construct(
ConnectionInterface $connection,
Grammar $grammar = null,
Processor $processor = null
) {
$this->connection = $connection;
$this->grammar = $grammar ?: $connection->getQueryGrammar();
$this->processor = $processor ?: $connection->getPostProcessor();
}
public function select($columns = ['*']) {
$this->columns = is_array($columns) ? $columns : func_get_args();
return $this;
}
public function selectRaw($expression, array $bindings = []) {
$this->addSelect(new Expression($expression));
if ($bindings) {
$this->addBinding($bindings, 'select');
}
return $this;
}
public function join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false) {
$join = new JoinClause($this, $type, $table);
// If the first "column" of the join is really a Closure instance the developer
// is trying to build a join with a complex "on" clause containing more than
// one condition, so we'll add the join and call a Closure with the query.
if ($first instanceof Closure) {
call_user_func($first, $join);
$this->joins[] = $join;
$this->addBinding($join->getBindings(), 'join');
}
// If the column is simply a string, we can assume the join simply has a basic
// "on" clause with a single condition. So we will just build the join with
// this simple join clauses attached to it. There is not a join callback.
else {
$method = $where ? 'where' : 'on';
$this->joins[] = $join->{$method}($first, $operator, $second);
$this->addBinding($join->getBindings(), 'join');
}
return $this;
}
public function toSql() {
return $this->grammar->compileSelect($this);
}
public function get($columns = ['*']): Collection {
return collect($this->onceWithColumns(Arr::wrap($columns), function () {
return $this->processor->processSelect($this, $this->runSelect());
}));
}
protected function onceWithColumns($columns, $callback) {
$original = $this->columns;
if (is_null($original)) {
$this->columns = $columns;
}
$result = $callback();
$this->columns = $original;
return $result;
}
#vendor\hyperf\utils\src\Functions.php
function collect($value = null)
{
return new Collection($value);
}
#Hyperf\Utils\Collection
protected static $proxies
= [
'average',
'avg',
'contains',
'each',
'every',
'filter',
'first',
'flatMap',
'groupBy',
'keyBy',
'map',
'max',
'min',
'partition',
'reject',
'sortBy',
'sortByDesc',
'sum',
'unique',
];
public function __get(string $key)
{
if (! in_array($key, static::$proxies)) {
throw new Exception("Property [{$key}] does not exist on this collection instance.");
}
return new HigherOrderCollectionProxy($this, $key);
}
public function __construct($items = [])
{
$this->items = $this->getArrayableItems($items);
}
public function all(): array
{
return $this->items;
}
public function first(callable $callback = null, $default = null)
{
return Arr::first($this->items, $callback, $default);
}
public function get($key, $default = null)
{
if ($this->offsetExists($key)) {
return $this->items[$key];
}
return value($default);
}