PageRenderTime 61ms CodeModel.GetById 35ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Illuminate/Database/Eloquent/Builder.php

https://bitbucket.org/mikebosire/framework
PHP | 496 lines | 206 code | 76 blank | 214 comment | 12 complexity | 48b3ba75a111b1b25cb623a311eefe4f MD5 | raw file
  1. <?php namespace Illuminate\Database\Eloquent;
  2. use Closure;
  3. use Illuminate\Database\Query\Builder as QueryBuilder;
  4. class Builder {
  5. /**
  6. * The base query builder instance.
  7. *
  8. * @var \Illuminate\Database\Query\Builder
  9. */
  10. protected $query;
  11. /**
  12. * The model being queried.
  13. *
  14. * @var \Illuminate\Database\Eloquent\Model
  15. */
  16. protected $model;
  17. /**
  18. * The relationships that should be eager loaded.
  19. *
  20. * @var array
  21. */
  22. protected $eagerLoad = array();
  23. /**
  24. * The methods that should be returned from query builder.
  25. *
  26. * @var array
  27. */
  28. protected $passthru = array(
  29. 'lists', 'insert', 'insertGetId', 'update', 'delete', 'increment',
  30. 'decrement', 'pluck', 'count', 'min', 'max', 'avg', 'sum',
  31. );
  32. /**
  33. * Create a new Eloquent query builder instance.
  34. *
  35. * @param \Illuminate\Database\Query\Builder $query
  36. * @return void
  37. */
  38. public function __construct(QueryBuilder $query)
  39. {
  40. $this->query = $query;
  41. }
  42. /**
  43. * Find a model by its primary key.
  44. *
  45. * @param mixed $id
  46. * @param array $columns
  47. * @return \Illuminate\Database\Eloquent\Model
  48. */
  49. public function find($id, $columns = array('*'))
  50. {
  51. $this->query->where($this->model->getKeyName(), '=', $id);
  52. return $this->first($columns);
  53. }
  54. /**
  55. * Execute the query and get the first result.
  56. *
  57. * @param array $columns
  58. * @return array
  59. */
  60. public function first($columns = array('*'))
  61. {
  62. return $this->take(1)->get($columns)->first();
  63. }
  64. /**
  65. * Execute the query and get the first result or throw an exception.
  66. *
  67. * @param array $columns
  68. * @return array
  69. */
  70. public function firstOrFail($columns = array('*'))
  71. {
  72. if ( ! is_null($model = $this->first($columns))) return $model;
  73. throw new ModelNotFoundException;
  74. }
  75. /**
  76. * Execute the query as a "select" statement.
  77. *
  78. * @param array $columns
  79. * @return \Illuminate\Database\Eloquent\Collection
  80. */
  81. public function get($columns = array('*'))
  82. {
  83. $models = $this->getModels($columns);
  84. // If we actually found models we will also eager load any relationships that
  85. // have been specified as needing to be eager loaded, which will solve the
  86. // n+1 query issue for the developers to avoid running a lot of queries.
  87. if (count($models) > 0)
  88. {
  89. $models = $this->eagerLoadRelations($models);
  90. }
  91. return $this->model->newCollection($models);
  92. }
  93. /**
  94. * Get an array with the values of a given column.
  95. *
  96. * @param string $column
  97. * @param string $key
  98. * @return array
  99. */
  100. public function lists($column, $key = null)
  101. {
  102. $results = $this->query->lists($column, $key);
  103. // If the model has a mutator for the requested column, we will spin through
  104. // the results and mutate the values so that the mutated version of these
  105. // columns are returned as you would expect from these Eloquent models.
  106. if ($this->model->hasGetMutator($column))
  107. {
  108. foreach ($results as $key => &$value)
  109. {
  110. $fill = array($column => $value);
  111. $value = $this->model->newFromBuilder($fill)->$column;
  112. }
  113. }
  114. return $results;
  115. }
  116. /**
  117. * Get a paginator for the "select" statement.
  118. *
  119. * @param int $perPage
  120. * @param array $columns
  121. * @return \Illuminate\Pagination\Paginator
  122. */
  123. public function paginate($perPage = null, $columns = array('*'))
  124. {
  125. $perPage = $perPage ?: $this->model->getPerPage();
  126. $paginator = $this->query->getConnection()->getPaginator();
  127. if (isset($this->query->groups))
  128. {
  129. return $this->groupedPaginate($paginator, $perPage, $columns);
  130. }
  131. else
  132. {
  133. return $this->ungroupedPaginate($paginator, $perPage, $columns);
  134. }
  135. }
  136. /**
  137. * Get a paginator for a grouped statement.
  138. *
  139. * @param \Illuminate\Pagination\Environment $paginator
  140. * @param int $perPage
  141. * @param array $columns
  142. * @return \Illuminate\Pagination\Paginator
  143. */
  144. protected function groupedPaginate($paginator, $perPage, $columns)
  145. {
  146. $results = $this->get($columns)->all();
  147. return $this->query->buildRawPaginator($paginator, $results, $perPage);
  148. }
  149. /**
  150. * Get a paginator for an ungrouped statement.
  151. *
  152. * @param \Illuminate\Pagination\Environment $paginator
  153. * @param int $perPage
  154. * @param array $columns
  155. * @return \Illuminate\Pagination\Paginator
  156. */
  157. protected function ungroupedPaginate($paginator, $perPage, $columns)
  158. {
  159. $total = $this->query->getPaginationCount();
  160. // Once we have the paginator we need to set the limit and offset values for
  161. // the query so we can get the properly paginated items. Once we have an
  162. // array of items we can create the paginator instances for the items.
  163. $page = $paginator->getCurrentPage();
  164. $this->query->forPage($page, $perPage);
  165. return $paginator->make($this->get($columns)->all(), $total, $perPage);
  166. }
  167. /**
  168. * Get the hydrated models without eager loading.
  169. *
  170. * @param array $columns
  171. * @return array
  172. */
  173. public function getModels($columns = array('*'))
  174. {
  175. // First, we will simply get the raw results from the query builders which we
  176. // can use to populate an array with Eloquent models. We will pass columns
  177. // that should be selected as well, which are typically just everything.
  178. $results = $this->query->get($columns);
  179. $connection = $this->model->getConnectionName();
  180. $models = array();
  181. // Once we have the results, we can spin through them and instantiate a fresh
  182. // model instance for each records we retrieved from the database. We will
  183. // also set the proper connection name for the model after we create it.
  184. foreach ($results as $result)
  185. {
  186. $models[] = $model = $this->model->newFromBuilder($result);
  187. $model->setConnection($connection);
  188. }
  189. return $models;
  190. }
  191. /**
  192. * Eager load the relationships for the models.
  193. *
  194. * @param array $models
  195. * @return array
  196. */
  197. public function eagerLoadRelations(array $models)
  198. {
  199. foreach ($this->eagerLoad as $name => $constraints)
  200. {
  201. // For nested eager loads we'll skip loading them here and they will be set as an
  202. // eager load on the query to retrieve the relation so that they will be eager
  203. // loaded on that query, because that is where they get hydrated as models.
  204. if (strpos($name, '.') === false)
  205. {
  206. $models = $this->loadRelation($models, $name, $constraints);
  207. }
  208. }
  209. return $models;
  210. }
  211. /**
  212. * Eagerly load the relationship on a set of models.
  213. *
  214. * @param string $relation
  215. * @param array $models
  216. * @param Closure $constraints
  217. * @return array
  218. */
  219. protected function loadRelation(array $models, $name, Closure $constraints)
  220. {
  221. // First we will "back up" the existing where conditions on the query so we can
  222. // add our eager constraints. Then we will merge the wheres that were on the
  223. // query back to it in order that any where conditions might be specified.
  224. $relation = $this->getRelation($name);
  225. list($wheres, $bindings) = $relation->getAndResetWheres();
  226. $relation->addEagerConstraints($models);
  227. // We allow the developers to specify constraints on eager loads and we'll just
  228. // call the constraints Closure, passing along the query so they will simply
  229. // do all they need to the queries, and even may specify non-where things.
  230. $relation->mergeWheres($wheres, $bindings);
  231. call_user_func($constraints, $relation);
  232. $models = $relation->initRelation($models, $name);
  233. // Once we have the results, we just match those back up to their parent models
  234. // using the relationship instance. Then we just return the finished arrays
  235. // of models which have been eagerly hydrated and are readied for return.
  236. $results = $relation->get();
  237. return $relation->match($models, $results, $name);
  238. }
  239. /**
  240. * Get the relation instance for the given relation name.
  241. *
  242. * @param string $relation
  243. * @return \Illuminate\Database\Eloquent\Relations\Relation
  244. */
  245. public function getRelation($relation)
  246. {
  247. $query = $this->getModel()->$relation();
  248. // If there are nested relationships set on the query, we will put those onto
  249. // the query instances so that they can be handled after this relationship
  250. // is loaded. In this way they will all trickle down as they are loaded.
  251. $nested = $this->nestedRelations($relation);
  252. if (count($nested) > 0)
  253. {
  254. $query->getQuery()->with($nested);
  255. }
  256. return $query;
  257. }
  258. /**
  259. * Get the deeply nested relations for a given top-level relation.
  260. *
  261. * @param string $relation
  262. * @return array
  263. */
  264. protected function nestedRelations($relation)
  265. {
  266. $nested = array();
  267. // We are basically looking for any relationships that are nested deeper than
  268. // the given top-level relationship. We will just check for any relations
  269. // that start with the given top relations and adds them to our arrays.
  270. foreach ($this->eagerLoad as $name => $constraints)
  271. {
  272. if (str_contains($name, '.') and starts_with($name, $relation) and $name != $relation)
  273. {
  274. $nested[substr($name, strlen($relation.'.'))] = $constraints;
  275. }
  276. }
  277. return $nested;
  278. }
  279. /**
  280. * Set the relationships that should be eager loaded.
  281. *
  282. * @param dynamic $relation
  283. * @return \Illuminate\Database\Eloquent\Builder
  284. */
  285. public function with($relations)
  286. {
  287. if (is_string($relations)) $relations = func_get_args();
  288. $this->eagerLoad = $this->parseRelations($relations);
  289. return $this;
  290. }
  291. /**
  292. * Parse a list of relations into individuals.
  293. *
  294. * @param array $relations
  295. * @return array
  296. */
  297. protected function parseRelations(array $relations)
  298. {
  299. $results = array();
  300. foreach ($relations as $name => $constraints)
  301. {
  302. // If the "relation" value is actually a numeric key, we can assume that no
  303. // constraints have been specified for the eager load and we'll just put
  304. // an empty Closure with the loader so that we can treat all the same.
  305. if (is_numeric($name))
  306. {
  307. $f = function() {};
  308. list($name, $constraints) = array($constraints, $f);
  309. }
  310. // We need to separate out any nested includes. Which allows the developers
  311. // to load deep relationships using "dots" without stating each level of
  312. // the relationship with its own key in the array of eager load names.
  313. $results = $this->parseNestedRelations($name, $results);
  314. $results[$name] = $constraints;
  315. }
  316. return $results;
  317. }
  318. /**
  319. * Parse the nested relationships in a relation.
  320. *
  321. * @param string $name
  322. * @param array $results
  323. * @return array
  324. */
  325. protected function parseNestedRelations($name, $results)
  326. {
  327. $progress = array();
  328. // If the relation has already been set on the result array, we will not set it
  329. // again, since that would override any constraints that were already placed
  330. // on the relationships. We will only set the ones that are not specified.
  331. foreach (explode('.', $name) as $segment)
  332. {
  333. $progress[] = $segment;
  334. if ( ! isset($results[$last = implode('.', $progress)]))
  335. {
  336. $results[$last] = function() {};
  337. }
  338. }
  339. return $results;
  340. }
  341. /**
  342. * Get the underlying query builder instance.
  343. *
  344. * @return \Illuminate\Database\Query\Builder
  345. */
  346. public function getQuery()
  347. {
  348. return $this->query;
  349. }
  350. /**
  351. * Set the underlying query builder instance.
  352. *
  353. * @param \Illuminate\Database\Query\Builder $query
  354. * @return void
  355. */
  356. public function setQuery($query)
  357. {
  358. $this->query = $query;
  359. }
  360. /**
  361. * Get the relationships being eagerly loaded.
  362. *
  363. * @return array
  364. */
  365. public function getEagerLoads()
  366. {
  367. return $this->eagerLoad;
  368. }
  369. /**
  370. * Set the relationships being eagerly loaded.
  371. *
  372. * @param array $eagerLoad
  373. * @return void
  374. */
  375. public function setEagerLoads(array $eagerLoad)
  376. {
  377. $this->eagerLoad = $eagerLoad;
  378. }
  379. /**
  380. * Get the model instance being queried.
  381. *
  382. * @return \Illuminate\Database\Eloquent\Model
  383. */
  384. public function getModel()
  385. {
  386. return $this->model;
  387. }
  388. /**
  389. * Set a model instance for the model being queried.
  390. *
  391. * @param \Illuminate\Database\Eloquent\Model $model
  392. * @return \Illuminate\Database\Eloquent\Builder
  393. */
  394. public function setModel(Model $model)
  395. {
  396. $this->model = $model;
  397. $this->query->from($model->getTable());
  398. return $this;
  399. }
  400. /**
  401. * Dynamically handle calls into the query instance.
  402. *
  403. * @param string $method
  404. * @param array $parameters
  405. * @return mixed
  406. */
  407. public function __call($method, $parameters)
  408. {
  409. if (method_exists($this->model, $scope = 'scope'.ucfirst($method)))
  410. {
  411. array_unshift($parameters, $this);
  412. call_user_func_array(array($this->model, $scope), $parameters);
  413. }
  414. else
  415. {
  416. $result = call_user_func_array(array($this->query, $method), $parameters);
  417. }
  418. return in_array($method, $this->passthru) ? $result : $this;
  419. }
  420. }