<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.4.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\ORM\Association\Loader;
use RuntimeException;
/**
* Implements the logic for loading an association using a SELECT query and a pivot table
*
* @internal
*/
class SelectWithPivotLoader extends SelectLoader
{
/**
* The name of the junction association
*
* @var string
*/
protected $junctionAssociationName;
/**
* The property name for the junction association, where its results should be nested at.
*
* @var string
*/
protected $junctionProperty;
/**
* The junction association instance
*
* @var \Cake\ORM\Association\HasMany
*/
protected $junctionAssoc;
/**
* Custom conditions for the junction association
*
* @var string|array|\Cake\Database\ExpressionInterface|callable|null
*/
protected $junctionConditions;
/**
* {@inheritDoc}
*
*/
public function __construct(array $options)
{
parent::__construct($options);
$this->junctionAssociationName = $options['junctionAssociationName'];
$this->junctionProperty = $options['junctionProperty'];
$this->junctionAssoc = $options['junctionAssoc'];
$this->junctionConditions = $options['junctionConditions'];
}
/**
* Auxiliary function to construct a new Query object to return all the records
* in the target table that are associated to those specified in $options from
* the source table.
*
* This is used for eager loading records on the target table based on conditions.
*
* @param array $options options accepted by eagerLoader()
* @return \Cake\ORM\Query
* @throws \InvalidArgumentException When a key is required for associations but not selected.
*/
protected function _buildQuery($options)
{
$name = $this->junctionAssociationName;
$assoc = $this->junctionAssoc;
$queryBuilder = false;
if (!empty($options['queryBuilder'])) {
$queryBuilder = $options['queryBuilder'];
unset($options['queryBuilder']);
}
$query = parent::_buildQuery($options);
if ($queryBuilder) {
$query = $queryBuilder($query);
}
if ($query->isAutoFieldsEnabled() === null) {
$query->enableAutoFields($query->clause('select') === []);
}
// Ensure that association conditions are applied
// and that the required keys are in the selected columns.
$tempName = $this->alias . '_CJoin';
$schema = $assoc->getSchema();
$joinFields = $types = [];
foreach ($schema->typeMap() as $f => $type) {
$key = $tempName . '__' . $f;
$joinFields[$key] = "$name.$f";
$types[$key] = $type;
}
$query
->where($this->junctionConditions)
->select($joinFields);
$query
->getEagerLoader()
->addToJoinsMap($tempName, $assoc, false, $this->junctionProperty);
$assoc->attachTo($query, [
'aliasPath' => $assoc->getAlias(),
'includeFields' => false,
'propertyPath' => $this->junctionProperty,
]);
$query->getTypeMap()->addDefaults($types);
return $query;
}
/**
* @inheritDoc
*/
protected function _assertFieldsPresent($fetchQuery, $key)
{
// _buildQuery() manually adds in required fields from junction table
}
/**
* Generates a string used as a table field that contains the values upon
* which the filter should be applied
*
* @param array $options the options to use for getting the link field.
* @return string|string[]
*/
protected function _linkField($options)
{
$links = [];
$name = $this->junctionAssociationName;
foreach ((array)$options['foreignKey'] as $key) {
$links[] = sprintf('%s.%s', $name, $key);
}
if (count($links) === 1) {
return $links[0];
}
return $links;
}
/**
* Builds an array containing the results from fetchQuery indexed by
* the foreignKey value corresponding to this association.
*
* @param \Cake\ORM\Query $fetchQuery The query to get results from
* @param array $options The options passed to the eager loader
* @return array
* @throws \RuntimeException when the association property is not part of the results set.
*/
protected function _buildResultMap($fetchQuery, $options)
{
$resultMap = [];
$key = (array)$options['foreignKey'];
foreach ($fetchQuery->all() as $result) {
if (!isset($result[$this->junctionProperty])) {
throw new RuntimeException(sprintf(
'"%s" is missing from the belongsToMany results. Results cannot be created.',
$this->junctionProperty
));
}
$values = [];
foreach ($key as $k) {
$values[] = $result[$this->junctionProperty][$k];
}
$resultMap[implode(';', $values)][] = $result;
}
return $resultMap;
}
}