Builder.php 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of Hyperf.
  5. *
  6. * @link https://www.hyperf.io
  7. * @document https://hyperf.wiki
  8. * @contact group@hyperf.io
  9. * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
  10. */
  11. namespace Hyperf\Database\Model;
  12. use BadMethodCallException;
  13. use Closure;
  14. use Generator;
  15. use Hyperf\Collection\Arr;
  16. use Hyperf\Collection\Collection;
  17. use Hyperf\Contract\Arrayable;
  18. use Hyperf\Contract\LengthAwarePaginatorInterface;
  19. use Hyperf\Contract\PaginatorInterface;
  20. use Hyperf\Database\Concerns\BuildsQueries;
  21. use Hyperf\Database\Model\Collection as ModelCollection;
  22. use Hyperf\Database\Model\Relations\Relation;
  23. use Hyperf\Database\Query\Builder as QueryBuilder;
  24. use Hyperf\Paginator\Paginator;
  25. use Hyperf\Stringable\Str;
  26. use Hyperf\Support\Traits\ForwardsCalls;
  27. use InvalidArgumentException;
  28. use ReflectionClass;
  29. use ReflectionMethod;
  30. use RuntimeException;
  31. use function Hyperf\Collection\collect;
  32. use function Hyperf\Tappable\tap;
  33. /**
  34. * @mixin \Hyperf\Database\Query\Builder
  35. */
  36. class Builder
  37. {
  38. use BuildsQueries;
  39. use ForwardsCalls;
  40. use Concerns\QueriesRelationships;
  41. /**
  42. * The base query builder instance.
  43. *
  44. * @var QueryBuilder
  45. */
  46. protected $query;
  47. /**
  48. * The model being queried.
  49. *
  50. * @var Model
  51. */
  52. protected $model;
  53. /**
  54. * The relationships that should be eager loaded.
  55. *
  56. * @var array
  57. */
  58. protected $eagerLoad = [];
  59. /**
  60. * All of the globally registered builder macros.
  61. *
  62. * @var array
  63. */
  64. protected static $macros = [];
  65. /**
  66. * All of the locally registered builder macros.
  67. *
  68. * @var array
  69. */
  70. protected $localMacros = [];
  71. /**
  72. * A replacement for the typical delete function.
  73. *
  74. * @var Closure
  75. */
  76. protected $onDelete;
  77. /**
  78. * The methods that should be returned from query builder.
  79. *
  80. * @var array
  81. */
  82. protected $passthru = [
  83. 'insert', 'insertGetId', 'getBindings', 'toSql', 'toRawSql', 'insertOrIgnore',
  84. 'exists', 'doesntExist', 'count', 'min', 'max', 'avg', 'average', 'sum', 'getConnection',
  85. 'upsert', 'updateOrInsert', 'insertOrIgnoreUsing',
  86. ];
  87. /**
  88. * Applied global scopes.
  89. *
  90. * @var array
  91. */
  92. protected $scopes = [];
  93. /**
  94. * Removed global scopes.
  95. *
  96. * @var array
  97. */
  98. protected $removedScopes = [];
  99. /**
  100. * Create a new Model query builder instance.
  101. */
  102. public function __construct(QueryBuilder $query)
  103. {
  104. $this->query = $query;
  105. }
  106. /**
  107. * Dynamically handle calls into the query instance.
  108. *
  109. * @param string $method
  110. * @param array $parameters
  111. */
  112. public function __call($method, $parameters)
  113. {
  114. if ($method === 'macro') {
  115. $this->localMacros[$parameters[0]] = $parameters[1];
  116. return;
  117. }
  118. if ($method === 'mixin') {
  119. return static::registerMixin($parameters[0], $parameters[1] ?? true);
  120. }
  121. if ($this->hasMacro($method)) {
  122. array_unshift($parameters, $this);
  123. return $this->localMacros[$method](...$parameters);
  124. }
  125. if (static::hasGlobalMacro($method)) {
  126. if (static::$macros[$method] instanceof Closure) {
  127. return call_user_func_array(static::$macros[$method]->bindTo($this, static::class), $parameters);
  128. }
  129. return call_user_func_array(static::$macros[$method], $parameters);
  130. }
  131. if (isset($this->model) && method_exists($this->model, $scope = 'scope' . ucfirst($method))) {
  132. return $this->callScope([$this->model, $scope], $parameters);
  133. }
  134. if (in_array($method, $this->passthru)) {
  135. return $this->toBase()->{$method}(...$parameters);
  136. }
  137. $this->query->{$method}(...$parameters);
  138. return $this;
  139. }
  140. /**
  141. * Dynamically handle calls into the query instance.
  142. *
  143. * @param string $method
  144. * @param array $parameters
  145. *
  146. * @throws BadMethodCallException
  147. */
  148. public static function __callStatic($method, $parameters)
  149. {
  150. if ($method === 'macro') {
  151. static::$macros[$parameters[0]] = $parameters[1];
  152. return;
  153. }
  154. if ($method === 'mixin') {
  155. return static::registerMixin($parameters[0], $parameters[1] ?? true);
  156. }
  157. if (! static::hasGlobalMacro($method)) {
  158. static::throwBadMethodCallException($method);
  159. }
  160. if (static::$macros[$method] instanceof Closure) {
  161. return call_user_func_array(Closure::bind(static::$macros[$method], null, static::class), $parameters);
  162. }
  163. return call_user_func_array(static::$macros[$method], $parameters);
  164. }
  165. /**
  166. * Force a clone of the underlying query builder when cloning.
  167. */
  168. public function __clone()
  169. {
  170. $this->query = clone $this->query;
  171. }
  172. /**
  173. * Clone the Model query builder.
  174. *
  175. * @return static
  176. */
  177. public function clone()
  178. {
  179. return clone $this;
  180. }
  181. /**
  182. * Create and return an un-saved model instance.
  183. *
  184. * @return Model
  185. */
  186. public function make(array $attributes = [])
  187. {
  188. return $this->newModelInstance($attributes);
  189. }
  190. /**
  191. * Register a new global scope.
  192. *
  193. * @param string $identifier
  194. * @param Closure|Scope $scope
  195. * @return $this
  196. */
  197. public function withGlobalScope($identifier, $scope)
  198. {
  199. $this->scopes[$identifier] = $scope;
  200. if (method_exists($scope, 'extend')) {
  201. $scope->extend($this);
  202. }
  203. return $this;
  204. }
  205. /**
  206. * Remove a registered global scope.
  207. *
  208. * @param Scope|string $scope
  209. * @return $this
  210. */
  211. public function withoutGlobalScope($scope)
  212. {
  213. if (! is_string($scope)) {
  214. $scope = get_class($scope);
  215. }
  216. unset($this->scopes[$scope]);
  217. $this->removedScopes[] = $scope;
  218. return $this;
  219. }
  220. /**
  221. * Remove all or passed registered global scopes.
  222. *
  223. * @return $this
  224. */
  225. public function withoutGlobalScopes(?array $scopes = null)
  226. {
  227. if (! is_array($scopes)) {
  228. $scopes = array_keys($this->scopes);
  229. }
  230. foreach ($scopes as $scope) {
  231. $this->withoutGlobalScope($scope);
  232. }
  233. return $this;
  234. }
  235. /**
  236. * Get an array of global scopes that were removed from the query.
  237. *
  238. * @return array
  239. */
  240. public function removedScopes()
  241. {
  242. return $this->removedScopes;
  243. }
  244. /**
  245. * Add a where clause on the primary key to the query.
  246. *
  247. * @param mixed $id
  248. * @return $this
  249. */
  250. public function whereKey($id)
  251. {
  252. if (is_array($id) || $id instanceof Arrayable) {
  253. $this->query->whereIn($this->model->getQualifiedKeyName(), $id);
  254. return $this;
  255. }
  256. return $this->where($this->model->getQualifiedKeyName(), '=', $id);
  257. }
  258. /**
  259. * Add a where clause on the primary key to the query.
  260. *
  261. * @param mixed $id
  262. * @return $this
  263. */
  264. public function whereKeyNot($id)
  265. {
  266. if (is_array($id) || $id instanceof Arrayable) {
  267. $this->query->whereNotIn($this->model->getQualifiedKeyName(), $id);
  268. return $this;
  269. }
  270. return $this->where($this->model->getQualifiedKeyName(), '!=', $id);
  271. }
  272. /**
  273. * Add a basic where clause to the query.
  274. *
  275. * @param array|Closure|string $column
  276. * @param string $boolean
  277. * @param null|mixed $operator
  278. * @param null|mixed $value
  279. * @return $this
  280. */
  281. public function where($column, $operator = null, $value = null, $boolean = 'and')
  282. {
  283. if ($column instanceof Closure) {
  284. $column($query = $this->model->newModelQuery());
  285. $this->query->addNestedWhereQuery($query->getQuery(), $boolean);
  286. } else {
  287. $this->query->where(...func_get_args());
  288. }
  289. return $this;
  290. }
  291. /**
  292. * Add an "or where" clause to the query.
  293. *
  294. * @param array|Closure|string $column
  295. * @param null|mixed $operator
  296. * @param null|mixed $value
  297. * @return Builder|static
  298. */
  299. public function orWhere($column, $operator = null, $value = null)
  300. {
  301. [$value, $operator] = $this->query->prepareValueAndOperator(
  302. $value,
  303. $operator,
  304. func_num_args() === 2
  305. );
  306. return $this->where($column, $operator, $value, 'or');
  307. }
  308. /**
  309. * Add an "order by" clause for a timestamp to the query.
  310. *
  311. * @param string $column
  312. * @return $this
  313. */
  314. public function latest($column = null)
  315. {
  316. if (is_null($column)) {
  317. $column = $this->model->getCreatedAtColumn() ?? 'created_at';
  318. }
  319. $this->query->latest($column);
  320. return $this;
  321. }
  322. /**
  323. * Add an "order by" clause for a timestamp to the query.
  324. *
  325. * @param string $column
  326. * @return $this
  327. */
  328. public function oldest($column = null)
  329. {
  330. if (is_null($column)) {
  331. $column = $this->model->getCreatedAtColumn() ?? 'created_at';
  332. }
  333. $this->query->oldest($column);
  334. return $this;
  335. }
  336. /**
  337. * Create a collection of models from plain arrays.
  338. *
  339. * @return ModelCollection
  340. */
  341. public function hydrate(array $items)
  342. {
  343. $instance = $this->newModelInstance();
  344. return $instance->newCollection(array_map(function ($item) use ($instance) {
  345. return $instance->newFromBuilder($item);
  346. }, $items));
  347. }
  348. /**
  349. * Create a collection of models from a raw query.
  350. *
  351. * @param string $query
  352. * @param array $bindings
  353. * @return ModelCollection
  354. */
  355. public function fromQuery($query, $bindings = [])
  356. {
  357. return $this->hydrate(
  358. $this->query->getConnection()->select($query, $bindings)
  359. );
  360. }
  361. /**
  362. * Find a model by its primary key.
  363. *
  364. * @param array $columns
  365. * @param array|int|string $id
  366. * @return null|Model|ModelCollection|static|static[]
  367. */
  368. public function find($id, $columns = ['*'])
  369. {
  370. if (is_array($id) || $id instanceof Arrayable) {
  371. return $this->findMany($id, $columns);
  372. }
  373. return $this->whereKey($id)->first($columns);
  374. }
  375. /**
  376. * Find multiple models by their primary keys.
  377. *
  378. * @param array|Arrayable $ids
  379. * @param array $columns
  380. * @return ModelCollection
  381. */
  382. public function findMany($ids, $columns = ['*'])
  383. {
  384. if (empty($ids)) {
  385. return $this->model->newCollection();
  386. }
  387. return $this->whereKey($ids)->get($columns);
  388. }
  389. /**
  390. * Find a model by its primary key or throw an exception.
  391. *
  392. * @param array $columns
  393. * @param mixed $id
  394. * @return Model|ModelCollection|static|static[]
  395. * @throws ModelNotFoundException
  396. */
  397. public function findOrFail($id, $columns = ['*'])
  398. {
  399. $result = $this->find($id, $columns);
  400. if (is_array($id)) {
  401. if (count($result) === count(array_unique($id))) {
  402. return $result;
  403. }
  404. } elseif (! is_null($result)) {
  405. return $result;
  406. }
  407. throw (new ModelNotFoundException())->setModel(
  408. get_class($this->model),
  409. $id
  410. );
  411. }
  412. /**
  413. * Find a model by its primary key or return fresh model instance.
  414. *
  415. * @param array $columns
  416. * @param mixed $id
  417. * @return Model|static
  418. */
  419. public function findOrNew($id, $columns = ['*'])
  420. {
  421. if (! is_null($model = $this->find($id, $columns))) {
  422. return $model;
  423. }
  424. return $this->newModelInstance();
  425. }
  426. /**
  427. * Get the first record matching the attributes or instantiate it.
  428. *
  429. * @return Model|static
  430. */
  431. public function firstOrNew(array $attributes, array $values = [])
  432. {
  433. if (! is_null($instance = $this->where($attributes)->first())) {
  434. return $instance;
  435. }
  436. return $this->newModelInstance($attributes + $values);
  437. }
  438. /**
  439. * Get the first record matching the attributes or create it.
  440. *
  441. * @return Model|static
  442. */
  443. public function firstOrCreate(array $attributes, array $values = [])
  444. {
  445. if (! is_null($instance = $this->where($attributes)->first())) {
  446. return $instance;
  447. }
  448. return tap($this->newModelInstance($attributes + $values), function ($instance) {
  449. $instance->save();
  450. });
  451. }
  452. /**
  453. * Create or update a record matching the attributes, and fill it with values.
  454. *
  455. * @return Model|static
  456. */
  457. public function updateOrCreate(array $attributes, array $values = [])
  458. {
  459. return tap($this->firstOrNew($attributes), function ($instance) use ($values) {
  460. $instance->fill($values)->save();
  461. });
  462. }
  463. /**
  464. * Execute the query and get the first result or throw an exception.
  465. *
  466. * @param array $columns
  467. * @return Model|static
  468. * @throws ModelNotFoundException
  469. */
  470. public function firstOrFail($columns = ['*'])
  471. {
  472. if (! is_null($model = $this->first($columns))) {
  473. return $model;
  474. }
  475. throw (new ModelNotFoundException())->setModel(get_class($this->model));
  476. }
  477. /**
  478. * Execute the query and get the first result or call a callback.
  479. *
  480. * @param array|Closure $columns
  481. * @return mixed|Model|static
  482. */
  483. public function firstOr($columns = ['*'], ?Closure $callback = null)
  484. {
  485. if ($columns instanceof Closure) {
  486. $callback = $columns;
  487. $columns = ['*'];
  488. }
  489. if (! is_null($model = $this->first($columns))) {
  490. return $model;
  491. }
  492. return call_user_func($callback);
  493. }
  494. /**
  495. * Get a single column's value from the first result of a query.
  496. *
  497. * @param string $column
  498. */
  499. public function value($column)
  500. {
  501. if ($result = $this->first([$column])) {
  502. return $result->{Str::afterLast($column, '.')};
  503. }
  504. }
  505. /**
  506. * Execute the query as a "select" statement.
  507. *
  508. * @param array $columns
  509. * @return ModelCollection|static[]
  510. */
  511. public function get($columns = ['*'])
  512. {
  513. $builder = $this->applyScopes();
  514. // If we actually found models we will also eager load any relationships that
  515. // have been specified as needing to be eager loaded, which will solve the
  516. // n+1 query issue for the developers to avoid running a lot of queries.
  517. if (count($models = $builder->getModels($columns)) > 0) {
  518. $models = $builder->eagerLoadRelations($models);
  519. }
  520. return $builder->getModel()->newCollection($models);
  521. }
  522. /**
  523. * Get the hydrated models without eager loading.
  524. *
  525. * @param array $columns
  526. * @return \Hyperf\Database\Model\Model[]|static[]
  527. */
  528. public function getModels($columns = ['*'])
  529. {
  530. return $this->model->hydrate(
  531. $this->query->get($columns)->all()
  532. )->all();
  533. }
  534. /**
  535. * Eager load the relationships for the models.
  536. *
  537. * @return array
  538. */
  539. public function eagerLoadRelations(array $models)
  540. {
  541. foreach ($this->eagerLoad as $name => $constraints) {
  542. // For nested eager loads we'll skip loading them here and they will be set as an
  543. // eager load on the query to retrieve the relation so that they will be eager
  544. // loaded on that query, because that is where they get hydrated as models.
  545. if (strpos($name, '.') === false) {
  546. $models = $this->eagerLoadRelation($models, $name, $constraints);
  547. }
  548. }
  549. return $models;
  550. }
  551. /**
  552. * Get the relation instance for the given relation name.
  553. *
  554. * @param string $name
  555. * @return Relation
  556. */
  557. public function getRelation($name)
  558. {
  559. // We want to run a relationship query without any constrains so that we will
  560. // not have to remove these where clauses manually which gets really hacky
  561. // and error prone. We don't want constraints because we add eager ones.
  562. $relation = Relation::noConstraints(function () use ($name) {
  563. try {
  564. return $this->getModel()->newInstance()->{$name}();
  565. } catch (BadMethodCallException $e) {
  566. throw RelationNotFoundException::make($this->getModel(), $name);
  567. }
  568. });
  569. $nested = $this->relationsNestedUnder($name);
  570. // If there are nested relationships set on the query, we will put those onto
  571. // the query instances so that they can be handled after this relationship
  572. // is loaded. In this way they will all trickle down as they are loaded.
  573. if (count($nested) > 0) {
  574. $relation->getQuery()->with($nested);
  575. }
  576. return $relation;
  577. }
  578. /**
  579. * Get a generator for the given query.
  580. *
  581. * @return Generator
  582. */
  583. public function cursor()
  584. {
  585. foreach ($this->applyScopes()->query->cursor() as $record) {
  586. yield $this->model->newFromBuilder($record);
  587. }
  588. }
  589. /**
  590. * Chunk the results of a query by comparing numeric IDs.
  591. *
  592. * @param int $count
  593. * @param null|string $column
  594. * @param null|string $alias
  595. * @return bool
  596. */
  597. public function chunkById($count, callable $callback, $column = null, $alias = null)
  598. {
  599. $column = $column ?? $this->getModel()->getKeyName();
  600. $alias = $alias ?? $column;
  601. $lastId = null;
  602. do {
  603. $clone = clone $this;
  604. // We'll execute the query for the given page and get the results. If there are
  605. // no results we can just break and return from here. When there are results
  606. // we will call the callback with the current chunk of these results here.
  607. $results = $clone->forPageAfterId($count, $lastId, $column)->get();
  608. $countResults = $results->count();
  609. if ($countResults == 0) {
  610. break;
  611. }
  612. // On each chunk result set, we will pass them to the callback and then let the
  613. // developer take care of everything within the callback, which allows us to
  614. // keep the memory low for spinning through large result sets for working.
  615. if ($callback($results) === false) {
  616. return false;
  617. }
  618. $lastId = $results->last()->{$alias};
  619. if ($lastId === null) {
  620. throw new RuntimeException("The chunkById operation was aborted because the [{$alias}] column is not present in the query result.");
  621. }
  622. unset($results);
  623. } while ($countResults == $count);
  624. return true;
  625. }
  626. /**
  627. * Get an array with the values of a given column.
  628. *
  629. * @param string $column
  630. * @param null|string $key
  631. * @return Collection
  632. */
  633. public function pluck($column, $key = null)
  634. {
  635. $results = $this->toBase()->pluck($column, $key);
  636. // If the model has a mutator for the requested column, we will spin through
  637. // the results and mutate the values so that the mutated version of these
  638. // columns are returned as you would expect from these Model models.
  639. if (! $this->model->hasGetMutator($column)
  640. && ! $this->model->hasCast($column)
  641. && ! in_array($column, $this->model->getDates())) {
  642. return $results;
  643. }
  644. return $results->map(function ($value) use ($column) {
  645. return $this->model->newFromBuilder([$column => $value])->{$column};
  646. });
  647. }
  648. /**
  649. * Paginate the given query.
  650. *
  651. * @throws InvalidArgumentException
  652. */
  653. public function paginate(?int $perPage = null, array $columns = ['*'], string $pageName = 'page', ?int $page = null): LengthAwarePaginatorInterface
  654. {
  655. $page = $page ?: Paginator::resolveCurrentPage($pageName);
  656. $perPage = $perPage ?: $this->model->getPerPage();
  657. $results = ($total = $this->toBase()->getCountForPagination())
  658. ? $this->forPage($page, $perPage)->get($columns)
  659. : $this->model->newCollection();
  660. return $this->paginator($results, $total, $perPage, $page, [
  661. 'path' => Paginator::resolveCurrentPath(),
  662. 'pageName' => $pageName,
  663. ]);
  664. }
  665. /**
  666. * Paginate the given query into a simple paginator.
  667. *
  668. * @param int $perPage
  669. * @param array $columns
  670. * @param string $pageName
  671. * @param null|int $page
  672. * @return PaginatorInterface
  673. */
  674. public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
  675. {
  676. $page = $page ?: Paginator::resolveCurrentPage($pageName);
  677. $perPage = $perPage ?: $this->model->getPerPage();
  678. // Next we will set the limit and offset for this query so that when we get the
  679. // results we get the proper section of results. Then, we'll create the full
  680. // paginator instances for these results with the given page and per page.
  681. $this->skip(($page - 1) * $perPage)->take($perPage + 1);
  682. return $this->simplePaginator($this->get($columns), $perPage, $page, [
  683. 'path' => Paginator::resolveCurrentPath(),
  684. 'pageName' => $pageName,
  685. ]);
  686. }
  687. /**
  688. * Save a new model and return the instance.
  689. *
  690. * @return $this|\Hyperf\Database\Model\Model
  691. */
  692. public function create(array $attributes = [])
  693. {
  694. return tap($this->newModelInstance($attributes), function ($instance) {
  695. $instance->save();
  696. });
  697. }
  698. /**
  699. * Save a new model and return the instance. Allow mass-assignment.
  700. *
  701. * @return $this|\Hyperf\Database\Model\Model
  702. */
  703. public function forceCreate(array $attributes)
  704. {
  705. return $this->model->unguarded(function () use ($attributes) {
  706. return $this->newModelInstance()->create($attributes);
  707. });
  708. }
  709. /**
  710. * Update a record in the database.
  711. *
  712. * @return int
  713. */
  714. public function update(array $values)
  715. {
  716. return $this->toBase()->update($this->addUpdatedAtColumn($values));
  717. }
  718. /**
  719. * Increment a column's value by a given amount.
  720. *
  721. * @param string $column
  722. * @param float|int $amount
  723. * @return int
  724. */
  725. public function increment($column, $amount = 1, array $extra = [])
  726. {
  727. return $this->toBase()->increment(
  728. $column,
  729. $amount,
  730. $this->addUpdatedAtColumn($extra)
  731. );
  732. }
  733. /**
  734. * Decrement a column's value by a given amount.
  735. *
  736. * @param string $column
  737. * @param float|int $amount
  738. * @return int
  739. */
  740. public function decrement($column, $amount = 1, array $extra = [])
  741. {
  742. return $this->toBase()->decrement(
  743. $column,
  744. $amount,
  745. $this->addUpdatedAtColumn($extra)
  746. );
  747. }
  748. /**
  749. * Delete a record from the database.
  750. */
  751. public function delete()
  752. {
  753. if (isset($this->onDelete)) {
  754. return call_user_func($this->onDelete, $this);
  755. }
  756. return $this->toBase()->delete();
  757. }
  758. /**
  759. * Run the default delete function on the builder.
  760. *
  761. * Since we do not apply scopes here, the row will actually be deleted.
  762. */
  763. public function forceDelete()
  764. {
  765. return $this->query->delete();
  766. }
  767. /**
  768. * Register a replacement for the default delete function.
  769. */
  770. public function onDelete(Closure $callback)
  771. {
  772. $this->onDelete = $callback;
  773. }
  774. /**
  775. * Call the given local model scopes.
  776. */
  777. public function scopes(array $scopes)
  778. {
  779. $builder = $this;
  780. foreach ($scopes as $scope => $parameters) {
  781. // If the scope key is an integer, then the scope was passed as the value and
  782. // the parameter list is empty, so we will format the scope name and these
  783. // parameters here. Then, we'll be ready to call the scope on the model.
  784. if (is_int($scope)) {
  785. [$scope, $parameters] = [$parameters, []];
  786. }
  787. // Next we'll pass the scope callback to the callScope method which will take
  788. // care of grouping the "wheres" properly so the logical order doesn't get
  789. // messed up when adding scopes. Then we'll return back out the builder.
  790. $builder = $builder->callScope(
  791. [$this->model, 'scope' . ucfirst($scope)],
  792. (array) $parameters
  793. );
  794. }
  795. return $builder;
  796. }
  797. /**
  798. * Apply the scopes to the Model builder instance and return it.
  799. *
  800. * @return Builder|static
  801. */
  802. public function applyScopes()
  803. {
  804. if (! $this->scopes) {
  805. return $this;
  806. }
  807. $builder = clone $this;
  808. foreach ($this->scopes as $identifier => $scope) {
  809. if (! isset($builder->scopes[$identifier])) {
  810. continue;
  811. }
  812. $builder->callScope(function (Builder $builder) use ($scope) {
  813. // If the scope is a Closure we will just go ahead and call the scope with the
  814. // builder instance. The "callScope" method will properly group the clauses
  815. // that are added to this query so "where" clauses maintain proper logic.
  816. if ($scope instanceof Closure) {
  817. $scope($builder);
  818. }
  819. // If the scope is a scope object, we will call the apply method on this scope
  820. // passing in the builder and the model instance. After we run all of these
  821. // scopes we will return back the builder instance to the outside caller.
  822. if ($scope instanceof Scope) {
  823. $scope->apply($builder, $this->getModel());
  824. }
  825. });
  826. }
  827. return $builder;
  828. }
  829. /**
  830. * Set the relationships that should be eager loaded.
  831. *
  832. * @param mixed $relations
  833. * @return $this
  834. */
  835. public function with($relations)
  836. {
  837. $eagerLoad = $this->parseWithRelations(is_string($relations) ? func_get_args() : $relations);
  838. $this->eagerLoad = array_merge($this->eagerLoad, $eagerLoad);
  839. return $this;
  840. }
  841. /**
  842. * Prevent the specified relations from being eager loaded.
  843. *
  844. * @param mixed $relations
  845. * @return $this
  846. */
  847. public function without($relations)
  848. {
  849. $this->eagerLoad = array_diff_key($this->eagerLoad, array_flip(
  850. is_string($relations) ? func_get_args() : $relations
  851. ));
  852. return $this;
  853. }
  854. /**
  855. * Create a new instance of the model being queried.
  856. *
  857. * @param array $attributes
  858. * @return Model|static
  859. */
  860. public function newModelInstance($attributes = [])
  861. {
  862. return $this->model->newInstance($attributes)->setConnection(
  863. $this->query->getConnection()->getName()
  864. );
  865. }
  866. /**
  867. * Apply query-time casts to the model instance.
  868. */
  869. public function withCasts(array $casts): static
  870. {
  871. $this->model->mergeCasts($casts);
  872. return $this;
  873. }
  874. /**
  875. * Get the underlying query builder instance.
  876. *
  877. * @return QueryBuilder
  878. */
  879. public function getQuery()
  880. {
  881. return $this->query;
  882. }
  883. /**
  884. * Set the underlying query builder instance.
  885. *
  886. * @param QueryBuilder $query
  887. * @return $this
  888. */
  889. public function setQuery($query)
  890. {
  891. $this->query = $query;
  892. return $this;
  893. }
  894. /**
  895. * Get a base query builder instance.
  896. *
  897. * @return QueryBuilder
  898. */
  899. public function toBase()
  900. {
  901. return $this->applyScopes()->getQuery();
  902. }
  903. /**
  904. * Get the relationships being eagerly loaded.
  905. *
  906. * @return array
  907. */
  908. public function getEagerLoads()
  909. {
  910. return $this->eagerLoad;
  911. }
  912. /**
  913. * Set the relationships being eagerly loaded.
  914. *
  915. * @return $this
  916. */
  917. public function setEagerLoads(array $eagerLoad)
  918. {
  919. $this->eagerLoad = $eagerLoad;
  920. return $this;
  921. }
  922. /**
  923. * Get the model instance being queried.
  924. *
  925. * @return Model|static
  926. */
  927. public function getModel()
  928. {
  929. return $this->model;
  930. }
  931. /**
  932. * Set a model instance for the model being queried.
  933. *
  934. * @return $this
  935. */
  936. public function setModel(Model $model)
  937. {
  938. $this->model = $model;
  939. $this->query->from($model->getTable());
  940. return $this;
  941. }
  942. /**
  943. * Qualify the given column name by the model's table.
  944. *
  945. * @param string $column
  946. * @return string
  947. */
  948. public function qualifyColumn($column)
  949. {
  950. return $this->model->qualifyColumn($column);
  951. }
  952. /**
  953. * Get the given macro by name.
  954. *
  955. * @param string $name
  956. * @return Closure
  957. */
  958. public function getMacro($name)
  959. {
  960. return Arr::get($this->localMacros, $name);
  961. }
  962. /**
  963. * Checks if a macro is registered.
  964. *
  965. * @param string $name
  966. * @return bool
  967. */
  968. public function hasMacro($name)
  969. {
  970. return isset($this->localMacros[$name]);
  971. }
  972. /**
  973. * Get the given global macro by name.
  974. *
  975. * @param string $name
  976. * @return Closure
  977. */
  978. public static function getGlobalMacro($name)
  979. {
  980. return Arr::get(static::$macros, $name);
  981. }
  982. /**
  983. * Checks if a global macro is registered.
  984. *
  985. * @param string $name
  986. * @return bool
  987. */
  988. public static function hasGlobalMacro($name)
  989. {
  990. return isset(static::$macros[$name]);
  991. }
  992. /**
  993. * Register the given mixin with the builder.
  994. *
  995. * @param string $mixin
  996. * @param bool $replace
  997. */
  998. protected static function registerMixin($mixin, $replace)
  999. {
  1000. $methods = (new ReflectionClass($mixin))->getMethods(
  1001. ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
  1002. );
  1003. foreach ($methods as $method) {
  1004. if ($replace || ! static::hasGlobalMacro($method->name)) {
  1005. static::macro($method->name, $method->invoke($mixin));
  1006. }
  1007. }
  1008. }
  1009. /**
  1010. * Eagerly load the relationship on a set of models.
  1011. *
  1012. * @param string $name
  1013. * @return array
  1014. */
  1015. protected function eagerLoadRelation(array $models, $name, Closure $constraints)
  1016. {
  1017. // First we will "back up" the existing where conditions on the query so we can
  1018. // add our eager constraints. Then we will merge the wheres that were on the
  1019. // query back to it in order that any where conditions might be specified.
  1020. $relation = $this->getRelation($name);
  1021. $relation->addEagerConstraints($models);
  1022. $constraints($relation);
  1023. // Once we have the results, we just match those back up to their parent models
  1024. // using the relationship instance. Then we just return the finished arrays
  1025. // of models which have been eagerly hydrated and are readied for return.
  1026. return $relation->match(
  1027. $relation->initRelation($models, $name),
  1028. $relation->getEager(),
  1029. $name
  1030. );
  1031. }
  1032. /**
  1033. * Get the deeply nested relations for a given top-level relation.
  1034. *
  1035. * @param string $relation
  1036. * @return array
  1037. */
  1038. protected function relationsNestedUnder($relation)
  1039. {
  1040. $nested = [];
  1041. // We are basically looking for any relationships that are nested deeper than
  1042. // the given top-level relationship. We will just check for any relations
  1043. // that start with the given top relations and adds them to our arrays.
  1044. foreach ($this->eagerLoad as $name => $constraints) {
  1045. if ($this->isNestedUnder($relation, $name)) {
  1046. $nested[substr($name, strlen($relation . '.'))] = $constraints;
  1047. }
  1048. }
  1049. return $nested;
  1050. }
  1051. /**
  1052. * Determine if the relationship is nested.
  1053. *
  1054. * @param string $relation
  1055. * @param string $name
  1056. * @return bool
  1057. */
  1058. protected function isNestedUnder($relation, $name)
  1059. {
  1060. return Str::contains($name, '.') && Str::startsWith($name, $relation . '.');
  1061. }
  1062. /**
  1063. * Add a generic "order by" clause if the query doesn't already have one.
  1064. */
  1065. protected function enforceOrderBy()
  1066. {
  1067. if (empty($this->query->orders) && empty($this->query->unionOrders)) {
  1068. $this->orderBy($this->model->getQualifiedKeyName(), 'asc');
  1069. }
  1070. }
  1071. /**
  1072. * Add the "updated at" column to an array of values.
  1073. *
  1074. * @return array
  1075. */
  1076. protected function addUpdatedAtColumn(array $values)
  1077. {
  1078. if (! $this->model->usesTimestamps()
  1079. || is_null($this->model->getUpdatedAtColumn())) {
  1080. return $values;
  1081. }
  1082. $column = $this->model->getUpdatedAtColumn();
  1083. $values = array_merge(
  1084. [$column => $this->model->freshTimestampString()],
  1085. $values
  1086. );
  1087. $segments = preg_split('/\s+as\s+/i', $this->query->from);
  1088. $qualifiedColumn = end($segments) . '.' . $column;
  1089. $values[$qualifiedColumn] = $values[$column];
  1090. unset($values[$column]);
  1091. return $values;
  1092. }
  1093. /**
  1094. * Apply the given scope on the current builder instance.
  1095. *
  1096. * @param array $parameters
  1097. */
  1098. protected function callScope(callable $scope, $parameters = [])
  1099. {
  1100. array_unshift($parameters, $this);
  1101. $query = $this->getQuery();
  1102. // We will keep track of how many wheres are on the query before running the
  1103. // scope so that we can properly group the added scope constraints in the
  1104. // query as their own isolated nested where statement and avoid issues.
  1105. $originalWhereCount = is_null($query->wheres)
  1106. ? 0 : count($query->wheres);
  1107. $result = $scope(...array_values($parameters)) ?? $this;
  1108. if (count((array) $query->wheres) > $originalWhereCount) {
  1109. $this->addNewWheresWithinGroup($query, $originalWhereCount);
  1110. }
  1111. return $result;
  1112. }
  1113. /**
  1114. * Nest where conditions by slicing them at the given where count.
  1115. *
  1116. * @param int $originalWhereCount
  1117. */
  1118. protected function addNewWheresWithinGroup(QueryBuilder $query, $originalWhereCount)
  1119. {
  1120. // Here, we totally remove all of the where clauses since we are going to
  1121. // rebuild them as nested queries by slicing the groups of wheres into
  1122. // their own sections. This is to prevent any confusing logic order.
  1123. $allWheres = $query->wheres;
  1124. $query->wheres = [];
  1125. $this->groupWhereSliceForScope(
  1126. $query,
  1127. array_slice($allWheres, 0, $originalWhereCount)
  1128. );
  1129. $this->groupWhereSliceForScope(
  1130. $query,
  1131. array_slice($allWheres, $originalWhereCount)
  1132. );
  1133. }
  1134. /**
  1135. * Slice where conditions at the given offset and add them to the query as a nested condition.
  1136. *
  1137. * @param array $whereSlice
  1138. */
  1139. protected function groupWhereSliceForScope(QueryBuilder $query, $whereSlice)
  1140. {
  1141. $whereBooleans = collect($whereSlice)->pluck('boolean');
  1142. // Here we'll check if the given subset of where clauses contains any "or"
  1143. // booleans and in this case create a nested where expression. That way
  1144. // we don't add any unnecessary nesting thus keeping the query clean.
  1145. if ($whereBooleans->contains('or')) {
  1146. $query->wheres[] = $this->createNestedWhere(
  1147. $whereSlice,
  1148. $whereBooleans->first()
  1149. );
  1150. } else {
  1151. $query->wheres = array_merge($query->wheres, $whereSlice);
  1152. }
  1153. }
  1154. /**
  1155. * Create a where array with nested where conditions.
  1156. *
  1157. * @param array $whereSlice
  1158. * @param string $boolean
  1159. * @return array
  1160. */
  1161. protected function createNestedWhere($whereSlice, $boolean = 'and')
  1162. {
  1163. $whereGroup = $this->getQuery()->forNestedWhere();
  1164. $whereGroup->wheres = $whereSlice;
  1165. return ['type' => 'Nested', 'query' => $whereGroup, 'boolean' => $boolean];
  1166. }
  1167. /**
  1168. * Parse a list of relations into individuals.
  1169. *
  1170. * @return array
  1171. */
  1172. protected function parseWithRelations(array $relations)
  1173. {
  1174. $results = [];
  1175. foreach ($relations as $name => $constraints) {
  1176. // If the "name" value is a numeric key, we can assume that no
  1177. // constraints have been specified. We'll just put an empty
  1178. // Closure there, so that we can treat them all the same.
  1179. if (is_numeric($name)) {
  1180. $name = $constraints;
  1181. [$name, $constraints] = Str::contains($name, ':')
  1182. ? $this->createSelectWithConstraint($name)
  1183. : [$name, static function () {
  1184. }];
  1185. }
  1186. // We need to separate out any nested includes, which allows the developers
  1187. // to load deep relationships using "dots" without stating each level of
  1188. // the relationship with its own key in the array of eager-load names.
  1189. $results = $this->addNestedWiths($name, $results);
  1190. $results[$name] = $constraints;
  1191. }
  1192. return $results;
  1193. }
  1194. /**
  1195. * Create a constraint to select the given columns for the relation.
  1196. *
  1197. * @param string $name
  1198. * @return array
  1199. */
  1200. protected function createSelectWithConstraint($name)
  1201. {
  1202. return [explode(':', $name)[0], static function ($query) use ($name) {
  1203. $query->select(explode(',', explode(':', $name)[1]));
  1204. }];
  1205. }
  1206. /**
  1207. * Parse the nested relationships in a relation.
  1208. *
  1209. * @param string $name
  1210. * @param array $results
  1211. * @return array
  1212. */
  1213. protected function addNestedWiths($name, $results)
  1214. {
  1215. $progress = [];
  1216. // If the relation has already been set on the result array, we will not set it
  1217. // again, since that would override any constraints that were already placed
  1218. // on the relationships. We will only set the ones that are not specified.
  1219. foreach (explode('.', $name) as $segment) {
  1220. $progress[] = $segment;
  1221. if (! isset($results[$last = implode('.', $progress)])) {
  1222. $results[$last] = static function () {
  1223. };
  1224. }
  1225. }
  1226. return $results;
  1227. }
  1228. }