1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
11: * @link http://cakephp.org CakePHP(tm) Project
12: * @since 3.0.0
13: * @license http://www.opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\ORM\Association;
16:
17: use Cake\Database\Expression\IdentifierExpression;
18: use Cake\Datasource\EntityInterface;
19: use Cake\ORM\Association;
20: use Cake\ORM\Table;
21: use Cake\Utility\Inflector;
22: use RuntimeException;
23:
24: /**
25: * Represents an 1 - N relationship where the source side of the relation is
26: * related to only one record in the target table.
27: *
28: * An example of a BelongsTo association would be Article belongs to Author.
29: */
30: class BelongsTo extends Association
31: {
32:
33: use SelectableAssociationTrait;
34:
35: /**
36: * Valid strategies for this type of association
37: *
38: * @var array
39: */
40: protected $_validStrategies = [self::STRATEGY_JOIN, self::STRATEGY_SELECT];
41:
42: /**
43: * Sets the name of the field representing the foreign key to the target table.
44: * If no parameters are passed current field is returned
45: *
46: * @param string|null $key the key to be used to link both tables together
47: * @return string
48: */
49: public function foreignKey($key = null)
50: {
51: if ($key === null) {
52: if ($this->_foreignKey === null) {
53: $this->_foreignKey = $this->_modelKey($this->target()->alias());
54: }
55:
56: return $this->_foreignKey;
57: }
58:
59: return parent::foreignKey($key);
60: }
61:
62: /**
63: * Handle cascading deletes.
64: *
65: * BelongsTo associations are never cleared in a cascading delete scenario.
66: *
67: * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascaded delete.
68: * @param array $options The options for the original delete.
69: * @return bool Success.
70: */
71: public function cascadeDelete(EntityInterface $entity, array $options = [])
72: {
73: return true;
74: }
75:
76: /**
77: * Returns default property name based on association name.
78: *
79: * @return string
80: */
81: protected function _propertyName()
82: {
83: list(, $name) = pluginSplit($this->_name);
84:
85: return Inflector::underscore(Inflector::singularize($name));
86: }
87:
88: /**
89: * Returns whether or not the passed table is the owning side for this
90: * association. This means that rows in the 'target' table would miss important
91: * or required information if the row in 'source' did not exist.
92: *
93: * @param \Cake\ORM\Table $side The potential Table with ownership
94: * @return bool
95: */
96: public function isOwningSide(Table $side)
97: {
98: return $side === $this->target();
99: }
100:
101: /**
102: * Get the relationship type.
103: *
104: * @return string
105: */
106: public function type()
107: {
108: return self::MANY_TO_ONE;
109: }
110:
111: /**
112: * Takes an entity from the source table and looks if there is a field
113: * matching the property name for this association. The found entity will be
114: * saved on the target table for this association by passing supplied
115: * `$options`
116: *
117: * @param \Cake\Datasource\EntityInterface $entity an entity from the source table
118: * @param array|\ArrayObject $options options to be passed to the save method in
119: * the target table
120: * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns
121: * the saved entity
122: * @see \Cake\ORM\Table::save()
123: */
124: public function saveAssociated(EntityInterface $entity, array $options = [])
125: {
126: $targetEntity = $entity->get($this->property());
127: if (empty($targetEntity) || !($targetEntity instanceof EntityInterface)) {
128: return $entity;
129: }
130:
131: $table = $this->target();
132: $targetEntity = $table->save($targetEntity, $options);
133: if (!$targetEntity) {
134: return false;
135: }
136:
137: $properties = array_combine(
138: (array)$this->foreignKey(),
139: $targetEntity->extract((array)$this->bindingKey())
140: );
141: $entity->set($properties, ['guard' => false]);
142:
143: return $entity;
144: }
145:
146: /**
147: * Returns a single or multiple conditions to be appended to the generated join
148: * clause for getting the results on the target table.
149: *
150: * @param array $options list of options passed to attachTo method
151: * @return array
152: * @throws \RuntimeException if the number of columns in the foreignKey do not
153: * match the number of columns in the target table primaryKey
154: */
155: protected function _joinCondition($options)
156: {
157: $conditions = [];
158: $tAlias = $this->target()->alias();
159: $sAlias = $this->_sourceTable->alias();
160: $foreignKey = (array)$options['foreignKey'];
161: $bindingKey = (array)$this->bindingKey();
162:
163: if (count($foreignKey) !== count($bindingKey)) {
164: if (empty($bindingKey)) {
165: $msg = 'The "%s" table does not define a primary key. Please set one.';
166: throw new RuntimeException(sprintf($msg, $this->target()->table()));
167: }
168:
169: $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"';
170: throw new RuntimeException(sprintf(
171: $msg,
172: $this->_name,
173: implode(', ', $foreignKey),
174: implode(', ', $bindingKey)
175: ));
176: }
177:
178: foreach ($foreignKey as $k => $f) {
179: $field = sprintf('%s.%s', $tAlias, $bindingKey[$k]);
180: $value = new IdentifierExpression(sprintf('%s.%s', $sAlias, $f));
181: $conditions[$field] = $value;
182: }
183:
184: return $conditions;
185: }
186:
187: /**
188: * {@inheritDoc}
189: */
190: protected function _linkField($options)
191: {
192: $links = [];
193: $name = $this->alias();
194:
195: foreach ((array)$this->bindingKey() as $key) {
196: $links[] = sprintf('%s.%s', $name, $key);
197: }
198:
199: if (count($links) === 1) {
200: return $links[0];
201: }
202:
203: return $links;
204: }
205:
206: /**
207: * {@inheritDoc}
208: */
209: protected function _buildResultMap($fetchQuery, $options)
210: {
211: $resultMap = [];
212: $key = (array)$this->bindingKey();
213:
214: foreach ($fetchQuery->all() as $result) {
215: $values = [];
216: foreach ($key as $k) {
217: $values[] = $result[$k];
218: }
219: $resultMap[implode(';', $values)] = $result;
220: }
221:
222: return $resultMap;
223: }
224: }
225: