hyperf 二十三 分页

教程:Hyperf

参考:

hyperf 十、分页-CSDN博客

illuminate/database 使用 一_illuminate db类-CSDN博客

hyperf 二十二 数据库 模型关系-CSDN博客

一 分页查询

1.1 原理

根据文档,可以使用model的paginate()和query的paginate()。根据源码model中BelongsToMany和HasManyThrough都有paginate()方法,都调用Hyperf\Database\Query\Builder::paginate()。

在Hyperf\Database\Model\Model中__call()调用Hyperf\Database\Query\Builder方法,__callStatic()调用自身。所以Model::paginate(),就是先调用__callStatic(),再调用__call(),最后执行Hyperf\Database\Query\Builder::paginate()。

像教程里写的Model::where('gender',1)->paginate(10)其实也是用的Hyperf\Database\Query\Builder类中的方法。

和分页器相比,这种分页查询改不了每个分页对应路径。分页查询返回Hyperf\Paginator\LengthAwarePaginator类,可以继续操作,但是也不能修改path。

使用DB直接调用paginate,也是调用Hyperf\Database\Query\Builder,所以最后返回的也是Hyperf\Paginator\LengthAwarePaginator类。

1.2 测试

$info = User::paginate(2)->toArray();
var_dump($info);
$info = Db::table('userinfo')->paginate(2)->jsonSerialize();
var_dump($info);

 测试结果

array(12) {
  ["current_page"]=>
  int(1)
  ["data"]=>
  array(2) {
    [0]=>
    array(4) {
      ["id"]=>
      int(1)
      ["name"]=>
      string(3) "123"
      ["age"]=>
      int(22)
      ["deleted_at"]=>
      NULL
    }
    [1]=>
    array(4) {
      ["id"]=>
      int(2)
      ["name"]=>
      string(5) "name2"
      ["age"]=>
      int(13)
      ["deleted_at"]=>
      NULL
    }
  }
  ["first_page_url"]=>
  string(46) "http://127.0.0.1:9501/test/testpaginate?page=1"
  ["from"]=>
  int(1)
  ["last_page"]=>
  int(10)
  ["last_page_url"]=>
  string(47) "http://127.0.0.1:9501/test/testpaginate?page=10"
  ["next_page_url"]=>
  string(46) "http://127.0.0.1:9501/test/testpaginate?page=2"
  ["path"]=>
  string(39) "http://127.0.0.1:9501/test/testpaginate"
  ["per_page"]=>
  int(2)
  ["prev_page_url"]=>
  NULL
  ["to"]=>
  int(2)
  ["total"]=>
  int(19)
}



array(12) {
  ["current_page"]=>
  int(1)
  ["data"]=>
  array(2) {
    [0]=>
    object(stdClass)#1120 (4) {
      ["id"]=>
      int(1)
      ["name"]=>
      string(3) "123"
      ["age"]=>
      int(22)
      ["deleted_at"]=>
      NULL
    }
    [1]=>
    object(stdClass)#1116 (4) {
      ["id"]=>
      int(2)
      ["name"]=>
      string(5) "name2"
      ["age"]=>
      int(13)
      ["deleted_at"]=>
      NULL
    }
  }
  ["first_page_url"]=>
  string(46) "http://127.0.0.1:9501/test/testpaginate?page=1"
  ["from"]=>
  int(1)
  ["last_page"]=>
  int(10)
  ["last_page_url"]=>
  string(47) "http://127.0.0.1:9501/test/testpaginate?page=10"
  ["next_page_url"]=>
  string(46) "http://127.0.0.1:9501/test/testpaginate?page=2"
  ["path"]=>
  string(39) "http://127.0.0.1:9501/test/testpaginate"
  ["per_page"]=>
  int(2)
  ["prev_page_url"]=>
  NULL
  ["to"]=>
  int(2)
  ["total"]=>
  int(20)
}

1.3 源码

model调用

#Hyperf\Database\Model\Model
public function __call($method, $parameters) {
        if (in_array($method, ['increment', 'decrement'])) {
            return $this->{$method}(...$parameters);
        }

        return call([$this->newQuery(), $method], $parameters);
    }
 public static function __callStatic($method, $parameters) {
        return (new static())->{$method}(...$parameters);
    }
#Hyperf\Database\Query\Builder 
public function paginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null): LengthAwarePaginatorInterface {
        $page = $page ?: Paginator::resolveCurrentPage($pageName);
        $total = $this->getCountForPagination();
        $results = $total ? $this->forPage($page, $perPage)->get($columns) : collect();

        return $this->paginator($results, $total, $perPage, $page, [
            'path' => Paginator::resolveCurrentPath(),
            'pageName' => $pageName,
        ]);
    }
protected function paginator(Collection $items, int $total, int $perPage, int $currentPage, array $options): LengthAwarePaginatorInterface {
        $container = ApplicationContext::getContainer();
        if (!method_exists($container, 'make')) {
            throw new \RuntimeException('The DI container does not support make() method.');
        }
        return $container->make(LengthAwarePaginatorInterface::class, compact('items', 'total', 'perPage', 'currentPage', 'options'));
    }

二 模型事件

2.1 系统运行事件

其实就是系统运行位置的钩子,设置监听时使用。

Hyperf\Database\Events\QueryExecutedQuery 语句执行后
Hyperf\Database\Events\StatementPreparedSQL 语句 prepared 后
Hyperf\Database\Events\TransactionBeginning事务开启后
Hyperf\Database\Events\TransactionCommitted事务提交后
Hyperf\Database\Events\TransactionRolledBack事务回滚后

 设置监听

namespace App\Listener;

use Hyperf\Database\Events\QueryExecuted;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Logger\LoggerFactory;
use Hyperf\Utils\Arr;
use Hyperf\Utils\Str;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;

/**
 * @Listener
 */
class DbQueryExecutedListener implements ListenerInterface
{
    /**
     * @var LoggerInterface
     */
    private $logger;

    public function __construct(ContainerInterface $container)
    {
        $this->logger = $container->get(LoggerFactory::class)->get('sql');
    }

    public function listen(): array
    {
        return [
            QueryExecuted::class,
        ];
    }

    /**
     * @param QueryExecuted $event
     */
    public function process(object $event)
    {
        if ($event instanceof QueryExecuted) {
            $sql = $event->sql;
            if (! Arr::isAssoc($event->bindings)) {
                foreach ($event->bindings as $key => $value) {
                    $sql = Str::replaceFirst('?', "'{$value}'", $sql);
                }
            }

            $this->logger->info(sprintf('[%s] %s', $event->time, $sql));
        }
    }

 例子中是从源码复制,自定义需要在\config\autoload\listeners.php中设置。

2.2 模型事件

钩子函数

事件名触发实际是否阻断备注
booting模型首次加载前进程生命周期中只会触发一次
booted模型首次加载后进程生命周期中只会触发一次
retrieved填充数据后每当模型从 DB 或缓存查询出来后触发
creating数据创建时
created数据创建后
updating数据更新时
updated数据更新后
saving数据创建或更新时
saved数据创建或更新后
restoring软删除数据恢复时
restored软删除数据恢复后
deleting数据删除时
deleted数据删除后
forceDeleting数据强制删除时
forceDeleted数据强制删除后

 测试参考:hyperf 二十二 数据库 模型关系-CSDN博客中测试中设置的自定义多态关系的监听。

2.3 源码

2.3.1 系统事件

#Hyperf\Database\Connection
use Concerns\ManagesTransactions;
protected function fireModelEvent(string $event): ?object
    {
        $dispatcher = $this->getEventDispatcher();
        if (! $dispatcher instanceof EventDispatcherInterface) {
            return null;
        }

        $result = $this->fireCustomModelEvent($event);
        // If custom event does not exist, the fireCustomModelEvent() method will return null.
        if (! is_null($result)) {
            return $result;
        }

        // If the model is not running in Hyperf, then the listener method of model will not bind to the EventDispatcher automatically.
        $eventName = $this->getDefaultEvents()[$event];
        return $dispatcher->dispatch(new $eventName($this, $event));
    }
protected function getDefaultEvents(): array
    {
        return [
            'booting' => Booting::class,
            'booted' => Booted::class,
            'retrieved' => Retrieved::class,
            'creating' => Creating::class,
            'created' => Created::class,
            'updating' => Updating::class,
            'updated' => Updated::class,
            'saving' => Saving::class,
            'saved' => Saved::class,
            'restoring' => Restoring::class,
            'restored' => Restored::class,
            'deleting' => Deleting::class,
            'deleted' => Deleted::class,
            'forceDeleted' => ForceDeleted::class,
        ];
    }


public function logQuery(string $query, array $bindings, ?float $time = null, $result = null)
    {
        $this->event(new QueryExecuted($query, $bindings, $time, $this, $result));

        if ($this->loggingQueries) {
            $this->queryLog[] = compact('query', 'bindings', 'time');
        }
    }
protected function prepared(PDOStatement $statement)
    {
        $statement->setFetchMode($this->fetchMode);

        $this->event(new Events\StatementPrepared(
            $this,
            $statement
        ));

        return $statement;
    }
protected function fireConnectionEvent($event)
    {
        if (! isset($this->events)) {
            return;
        }

        switch ($event) {
            case 'beganTransaction':
                return $this->events->dispatch(new Events\TransactionBeginning($this));
            case 'committed':
                return $this->events->dispatch(new Events\TransactionCommitted($this));
            case 'rollingBack':
                return $this->events->dispatch(new Events\TransactionRolledBack($this));
        }
    }
protected function run(string $query, array $bindings, Closure $callback)
    {
        $this->reconnectIfMissingConnection();

        $start = microtime(true);

        // Here we will run this query. If an exception occurs we'll determine if it was
        // caused by a connection that has been lost. If that is the cause, we'll try
        // to re-establish connection and re-run the query with a fresh connection.
        try {
            $result = $this->runQueryCallback($query, $bindings, $callback);
        } catch (QueryException $e) {
            $result = $this->handleQueryException(
                $e,
                $query,
                $bindings,
                $callback
            );
        }

        // Once we have run the query we will calculate the time that it took to run and
        // then log the query, bindings, result and execution time so we will report them on
        // the event that the developer needs them. We'll log time in milliseconds.
        $this->logQuery(
            $query,
            $bindings,
            $this->getElapsedTime($start),
            $result
        );

        return $result;
    }
public function select(string $query, array $bindings = [], bool $useReadPdo = true): array
    {
        return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
            if ($this->pretending()) {
                return [];
            }

            // For select statements, we'll simply execute the query and return an array
            // of the database result set. Each element in the array will be a single
            // row from the database table, and will either be an array or objects.
            $statement = $this->prepared($this->getPdoForSelect($useReadPdo)
                ->prepare($query));

            $this->bindValues($statement, $this->prepareBindings($bindings));

            $statement->execute();

            return $statement->fetchAll();
        });
    }
#Hyperf\Database\Concerns\ManagesTransactions
public function beginTransaction(): void
    {
        $this->createTransaction();

        ++$this->transactions;

        $this->fireConnectionEvent('beganTransaction');
    }
public function commit(): void
    {
        if ($this->transactions == 1) {
            $this->getPdo()->commit();
        }

        $this->transactions = max(0, $this->transactions - 1);

        $this->fireConnectionEvent('committed');
    }
public function rollBack($toLevel = null): void
    {
        // We allow developers to rollback to a certain transaction level. We will verify
        // that this given transaction level is valid before attempting to rollback to
        // that level. If it's not we will just return out and not attempt anything.
        $toLevel = is_null($toLevel)
            ? $this->transactions - 1
            : $toLevel;

        if ($toLevel < 0 || $toLevel >= $this->transactions) {
            return;
        }

        // Next, we will actually perform this rollback within this database and fire the
        // rollback event. We will also set the current transaction level to the given
        // level that was passed into this method so it will be right from here out.
        try {
            $this->performRollBack($toLevel);
        } catch (Exception $e) {
            $this->handleRollBackException($e);
        }

        $this->transactions = $toLevel;

        $this->fireConnectionEvent('rollingBack');
    }
#Hyperf\Event\ListenerProvider
class ListenerProvider implements ListenerProviderInterface
{
    /**
     * @var ListenerData[]
     */
    public $listeners = [];

    /**
     * @param object $event An event for which to return the relevant listeners
     * @return iterable[callable] An iterable (array, iterator, or generator) of callables.  Each
     *                            callable MUST be type-compatible with $event.
     */
    public function getListenersForEvent($event): iterable
    {
        $queue = new SplPriorityQueue();
        foreach ($this->listeners as $listener) {
            if ($event instanceof $listener->event) {
                $queue->insert($listener->listener, $listener->priority);
            }
        }
        return $queue;
    }

    public function on(string $event, callable $listener, int $priority = 1): void
    {
        $this->listeners[] = new ListenerData($event, $listener, $priority);
    }
}
#Hyperf\Event\ListenerProviderFactory
public function __invoke(ContainerInterface $container)
    {
        $listenerProvider = new ListenerProvider();

        // Register config listeners.
        $this->registerConfig($listenerProvider, $container);

        // Register annotation listeners.
        $this->registerAnnotations($listenerProvider, $container);

        return $listenerProvider;
    }
private function registerAnnotations(ListenerProvider $provider, ContainerInterface $container): void
    {
        foreach (AnnotationCollector::list() as $className => $values) {
            /** @var Listener $annotation */
            if ($annotation = $values['_c'][Listener::class] ?? null) {
                $this->register($provider, $container, $className, (int) $annotation->priority);
            }
        }
    }
private function register(ListenerProvider $provider, ContainerInterface $container, string $listener, int $priority = 1): void
    {
        $instance = $container->get($listener);
        if ($instance instanceof ListenerInterface) {
            foreach ($instance->listen() as $event) {
                $provider->on($event, [$instance, 'process'], $priority);
            }
        }
    }

 2.3.2 模型事件

#Hyperf\Database\Model\Model
protected function bootIfNotBooted(): void {
        $booted = Booted::$container[static::class] ?? false;
        if (!$booted) {
            Booted::$container[static::class] = true;

            $this->fireModelEvent('booting');

            $this->boot();

            $this->fireModelEvent('booted');
        }
    }
protected function bootIfNotBooted(): void {
        $booted = Booted::$container[static::class] ?? false;
        if (!$booted) {
            Booted::$container[static::class] = true;

            $this->fireModelEvent('booting');

            $this->boot();

            $this->fireModelEvent('booted');
        }
    }
public function newFromBuilder($attributes = [], $connection = null) {
        $model = $this->newInstance([], true);

        $model->setRawAttributes((array) $attributes, true);

        $model->setConnection($connection ?: $this->getConnectionName());

        $model->fireModelEvent('retrieved', false);

        return $model;
    }
protected function performInsert(Builder $query) {
        if ($event = $this->fireModelEvent('creating')) {
            if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
                return false;
            }
        }

        // First we'll need to create a fresh query instance and touch the creation and
        // update timestamps on this model, which are maintained by us for developer
        // convenience. After, we will just continue saving these model instances.
        if ($this->usesTimestamps()) {
            $this->updateTimestamps();
        }

        // If the model has an incrementing key, we can use the "insertGetId" method on
        // the query builder, which will give us back the final inserted ID for this
        // table from the database. Not all tables have to be incrementing though.
        $attributes = $this->getAttributes();

        if ($this->getIncrementing()) {
            $this->insertAndSetId($query, $attributes);
        }

        // If the table isn't incrementing we'll simply insert these attributes as they
        // are. These attribute arrays must contain an "id" column previously placed
        // there by the developer as the manually determined key for these models.
        else {
            if (empty($attributes)) {
                return true;
            }

            $query->insert($attributes);
        }

        // We will go ahead and set the exists property to true, so that it is set when
        // the created event is fired, just in case the developer tries to update it
        // during the event. This will allow them to do so and run an update here.
        $this->exists = true;

        $this->wasRecentlyCreated = true;

        $this->fireModelEvent('created');

        return true;
    }
protected function performUpdate(Builder $query) {
        // If the updating event returns false, we will cancel the update operation so
        // developers can hook Validation systems into their models and cancel this
        // operation if the model does not pass validation. Otherwise, we update.
        if ($event = $this->fireModelEvent('updating')) {
            if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
                return false;
            }
        }

        // First we need to create a fresh query instance and touch the creation and
        // update timestamp on the model which are maintained by us for developer
        // convenience. Then we will just continue saving the model instances.
        if ($this->usesTimestamps()) {
            $this->updateTimestamps();
        }

        // Once we have run the update operation, we will fire the "updated" event for
        // this model instance. This will allow developers to hook into these after
        // models are updated, giving them a chance to do any special processing.
        $dirty = $this->getDirty();

        if (count($dirty) > 0) {
            $this->setKeysForSaveQuery($query)->update($dirty);

            $this->syncChanges();

            $this->fireModelEvent('updated');
        }

        return true;
    }
public function save(array $options = []): bool {
        $this->mergeAttributesFromClassCasts();

        $query = $this->newModelQuery();

        // If the "saving" event returns false we'll bail out of the save and return
        // false, indicating that the save failed. This provides a chance for any
        // listeners to cancel save operations if validations fail or whatever.
        if ($saving = $this->fireModelEvent('saving')) {
            if ($saving instanceof StoppableEventInterface && $saving->isPropagationStopped()) {
                return false;
            }
        }

        // If the model already exists in the database we can just update our record
        // that is already in this database using the current IDs in this "where"
        // clause to only update this model. Otherwise, we'll just insert them.
        if ($this->exists) {
            $saved = $this->isDirty() ? $this->performUpdate($query) : true;
        } else {
            // If the model is brand new, we'll insert it into our database and set the
            // ID attribute on the model to the value of the newly inserted row's ID
            // which is typically an auto-increment value managed by the database.
            $saved = $this->performInsert($query);

            if (!$this->getConnectionName() && $connection = $query->getConnection()) {
                $this->setConnection($connection->getName());
            }
        }

        // If the model is successfully saved, we need to do a few more things once
        // that is done. We will call the "saved" method here to run any actions
        // we need to happen after a model gets successfully saved right here.
        if ($saved) {
            $this->finishSave($options);
        }

        return $saved;
    }
protected function finishSave(array $options) {
        $this->fireModelEvent('saved');

        if ($this->isDirty() && ($options['touch'] ?? true)) {
            $this->touchOwners();
        }

        $this->syncOriginal();
    }
public function delete() {
        $this->mergeAttributesFromClassCasts();

        if (is_null($this->getKeyName())) {
            throw new Exception('No primary key defined on model.');
        }

        // If the model doesn't exist, there is nothing to delete so we'll just return
        // immediately and not do anything else. Otherwise, we will continue with a
        // deletion process on the model, firing the proper events, and so forth.
        if (!$this->exists) {
            return;
        }

        if ($event = $this->fireModelEvent('deleting')) {
            if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
                return false;
            }
        }

        // Here, we'll touch the owning models, verifying these timestamps get updated
        // for the models. This will allow any caching to get broken on the parents
        // by the timestamp. Then we will go ahead and delete the model instance.
        $this->touchOwners();

        $this->performDeleteOnModel();

        // Once the model has been deleted, we will fire off the deleted event so that
        // the developers may hook into post-delete operations. We will then return
        // a boolean true as the delete is presumably successful on the database.
        $this->fireModelEvent('deleted');

        return true;
    }
#Hyperf\Database\Model\SoftDeletes
public function restore()
    {
        // If the restoring event does not return false, we will proceed with this
        // restore operation. Otherwise, we bail out so the developer will stop
        // the restore totally. We will clear the deleted timestamp and save.
        if ($event = $this->fireModelEvent('restoring')) {
            if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
                return false;
            }
        }

        $this->{$this->getDeletedAtColumn()} = null;

        // Once we have saved the model, we will fire the "restored" event so this
        // developer will do anything they need to after a restore operation is
        // totally finished. Then we will return the result of the save call.
        $this->exists = true;

        $result = $this->save();

        $this->fireModelEvent('restored');

        return $result;
    }

可能是因为版本问题,没查到forceDeleting、forceDeleted位置。其次SoftDeletes::restore(),好像需要手动调用。

设置监听内容通过调用ListenerProviderFactory,执行通过Hyperf\Database\Connection::fireModelEvent()。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lsswear

感谢大佬打赏 q(≧▽≦q)

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值