| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434 |
- <?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.0.0
- * @license https://opensource.org/licenses/mit-license.php MIT License
- */
- namespace Cake\Test\TestCase\ORM\Association;
- use Cake\Database\Expression\IdentifierExpression;
- use Cake\Database\Expression\QueryExpression;
- use Cake\Database\TypeMap;
- use Cake\ORM\Association\HasOne;
- use Cake\ORM\Entity;
- use Cake\TestSuite\TestCase;
- /**
- * Tests HasOne class
- */
- class HasOneTest extends TestCase
- {
- /**
- * Fixtures to load
- *
- * @var array
- */
- public $fixtures = ['core.Articles', 'core.NullableAuthors', 'core.Users', 'core.Profiles'];
- /**
- * @var bool
- */
- protected $listenerCalled = false;
- /**
- * Set up
- *
- * @return void
- */
- public function setUp()
- {
- parent::setUp();
- $this->user = $this->getTableLocator()->get('Users');
- $this->profile = $this->getTableLocator()->get('Profiles');
- $this->listenerCalled = false;
- }
- /**
- * Tests that foreignKey() returns the correct configured value
- *
- * @group deprecated
- * @return void
- */
- public function testForeignKey()
- {
- $this->deprecated(function () {
- $assoc = new HasOne('Profiles', [
- 'sourceTable' => $this->user,
- ]);
- $this->assertEquals('user_id', $assoc->foreignKey());
- $this->assertEquals('another_key', $assoc->foreignKey('another_key'));
- $this->assertEquals('another_key', $assoc->foreignKey());
- });
- }
- /**
- * Tests that setForeignKey() returns the correct configured value
- *
- * @return void
- */
- public function testSetForeignKey()
- {
- $assoc = new HasOne('Profiles', [
- 'sourceTable' => $this->user,
- ]);
- $this->assertEquals('user_id', $assoc->getForeignKey());
- $this->assertEquals($assoc, $assoc->setForeignKey('another_key'));
- $this->assertEquals('another_key', $assoc->getForeignKey());
- }
- /**
- * Tests that the association reports it can be joined
- *
- * @return void
- */
- public function testCanBeJoined()
- {
- $assoc = new HasOne('Test');
- $this->assertTrue($assoc->canBeJoined());
- }
- /**
- * Tests that the correct join and fields are attached to a query depending on
- * the association config
- *
- * @return void
- */
- public function testAttachTo()
- {
- $config = [
- 'foreignKey' => 'user_id',
- 'sourceTable' => $this->user,
- 'targetTable' => $this->profile,
- 'property' => 'profile',
- 'joinType' => 'INNER',
- 'conditions' => ['Profiles.is_active' => true],
- ];
- $association = new HasOne('Profiles', $config);
- $query = $this->user->find();
- $association->attachTo($query);
- $results = $query->order('Users.id')->toArray();
- $this->assertCount(1, $results, 'Only one record because of conditions & join type');
- $this->assertSame('masters', $results[0]->Profiles['last_name']);
- }
- /**
- * Tests that it is possible to avoid fields inclusion for the associated table
- *
- * @return void
- */
- public function testAttachToNoFields()
- {
- $config = [
- 'sourceTable' => $this->user,
- 'targetTable' => $this->profile,
- 'conditions' => ['Profiles.is_active' => true],
- ];
- $association = new HasOne('Profiles', $config);
- $query = $this->user->query();
- $association->attachTo($query, ['includeFields' => false]);
- $this->assertEmpty($query->clause('select'));
- }
- /**
- * Tests that using hasOne with a table having a multi column primary
- * key will work if the foreign key is passed
- *
- * @return void
- */
- public function testAttachToMultiPrimaryKey()
- {
- $selectTypeMap = new TypeMap([
- 'Profiles.id' => 'integer',
- 'id' => 'integer',
- 'Profiles.first_name' => 'string',
- 'first_name' => 'string',
- 'Profiles.user_id' => 'integer',
- 'user_id' => 'integer',
- 'Profiles__first_name' => 'string',
- 'Profiles__user_id' => 'integer',
- 'Profiles__id' => 'integer',
- 'Profiles__last_name' => 'string',
- 'Profiles.last_name' => 'string',
- 'last_name' => 'string',
- 'Profiles__is_active' => 'boolean',
- 'Profiles.is_active' => 'boolean',
- 'is_active' => 'boolean',
- ]);
- $config = [
- 'sourceTable' => $this->user,
- 'targetTable' => $this->profile,
- 'conditions' => ['Profiles.is_active' => true],
- 'foreignKey' => ['user_id', 'user_site_id'],
- ];
- $this->user->setPrimaryKey(['id', 'site_id']);
- $association = new HasOne('Profiles', $config);
- $query = $this->getMockBuilder('\Cake\ORM\Query')
- ->setMethods(['join', 'select'])
- ->disableOriginalConstructor()
- ->getMock();
- $field1 = new IdentifierExpression('Profiles.user_id');
- $field2 = new IdentifierExpression('Profiles.user_site_id');
- $query->expects($this->once())->method('join')->with([
- 'Profiles' => [
- 'conditions' => new QueryExpression([
- 'Profiles.is_active' => true,
- ['Users.id' => $field1, 'Users.site_id' => $field2],
- ], $selectTypeMap),
- 'type' => 'LEFT',
- 'table' => 'profiles',
- ],
- ]);
- $query->expects($this->never())->method('select');
- $association->attachTo($query, ['includeFields' => false]);
- }
- /**
- * Tests that using hasOne with a table having a multi column primary
- * key will work if the foreign key is passed
- *
- * @return void
- */
- public function testAttachToMultiPrimaryKeyMismatch()
- {
- $this->expectException(\RuntimeException::class);
- $this->expectExceptionMessage('Cannot match provided foreignKey for "Profiles", got "(user_id)" but expected foreign key for "(id, site_id)"');
- $query = $this->getMockBuilder('\Cake\ORM\Query')
- ->setMethods(['join', 'select'])
- ->disableOriginalConstructor()
- ->getMock();
- $config = [
- 'sourceTable' => $this->user,
- 'targetTable' => $this->profile,
- 'conditions' => ['Profiles.is_active' => true],
- ];
- $this->user->setPrimaryKey(['id', 'site_id']);
- $association = new HasOne('Profiles', $config);
- $association->attachTo($query, ['includeFields' => false]);
- }
- /**
- * Test that saveAssociated() ignores non entity values.
- *
- * @return void
- */
- public function testSaveAssociatedOnlyEntities()
- {
- $mock = $this->getMockBuilder('Cake\ORM\Table')
- ->setMethods(['saveAssociated'])
- ->disableOriginalConstructor()
- ->getMock();
- $config = [
- 'sourceTable' => $this->user,
- 'targetTable' => $mock,
- ];
- $mock->expects($this->never())
- ->method('saveAssociated');
- $entity = new Entity([
- 'username' => 'Mark',
- 'email' => 'mark@example.com',
- 'profile' => ['twitter' => '@cakephp'],
- ]);
- $association = new HasOne('Profiles', $config);
- $result = $association->saveAssociated($entity);
- $this->assertSame($result, $entity);
- }
- /**
- * Tests that property is being set using the constructor options.
- *
- * @return void
- */
- public function testPropertyOption()
- {
- $config = ['propertyName' => 'thing_placeholder'];
- $association = new hasOne('Thing', $config);
- $this->assertEquals('thing_placeholder', $association->getProperty());
- }
- /**
- * Test that plugin names are omitted from property()
- *
- * @return void
- */
- public function testPropertyNoPlugin()
- {
- $config = [
- 'sourceTable' => $this->user,
- 'targetTable' => $this->profile,
- ];
- $association = new HasOne('Contacts.Profiles', $config);
- $this->assertEquals('profile', $association->getProperty());
- }
- /**
- * Tests that attaching an association to a query will trigger beforeFind
- * for the target table
- *
- * @return void
- */
- public function testAttachToBeforeFind()
- {
- $config = [
- 'foreignKey' => 'user_id',
- 'sourceTable' => $this->user,
- 'targetTable' => $this->profile,
- ];
- $query = $this->user->query();
- $this->listenerCalled = false;
- $this->profile->getEventManager()->on('Model.beforeFind', function ($event, $query, $options, $primary) {
- $this->listenerCalled = true;
- $this->assertInstanceOf('\Cake\Event\Event', $event);
- $this->assertInstanceOf('\Cake\ORM\Query', $query);
- $this->assertInstanceOf('\ArrayObject', $options);
- $this->assertFalse($primary);
- });
- $association = new HasOne('Profiles', $config);
- $association->attachTo($query);
- $this->assertTrue($this->listenerCalled, 'beforeFind event not fired.');
- }
- /**
- * Tests that attaching an association to a query will trigger beforeFind
- * for the target table
- *
- * @return void
- */
- public function testAttachToBeforeFindExtraOptions()
- {
- $config = [
- 'foreignKey' => 'user_id',
- 'sourceTable' => $this->user,
- 'targetTable' => $this->profile,
- ];
- $this->listenerCalled = false;
- $opts = new \ArrayObject(['something' => 'more']);
- $this->profile->getEventManager()->on(
- 'Model.beforeFind',
- function ($event, $query, $options, $primary) use ($opts) {
- $this->listenerCalled = true;
- $this->assertInstanceOf('\Cake\Event\Event', $event);
- $this->assertInstanceOf('\Cake\ORM\Query', $query);
- $this->assertEquals($options, $opts);
- $this->assertFalse($primary);
- }
- );
- $association = new HasOne('Profiles', $config);
- $query = $this->user->find();
- $association->attachTo($query, ['queryBuilder' => function ($q) {
- return $q->applyOptions(['something' => 'more']);
- }]);
- $this->assertTrue($this->listenerCalled, 'Event not fired');
- }
- /**
- * Test cascading deletes.
- *
- * @return void
- */
- public function testCascadeDelete()
- {
- $config = [
- 'dependent' => true,
- 'sourceTable' => $this->user,
- 'targetTable' => $this->profile,
- 'conditions' => ['Profiles.is_active' => true],
- 'cascadeCallbacks' => false,
- ];
- $association = new HasOne('Profiles', $config);
- $this->profile->getEventManager()->on('Model.beforeDelete', function () {
- $this->fail('Callbacks should not be triggered when callbacks do not cascade.');
- });
- $entity = new Entity(['id' => 1]);
- $association->cascadeDelete($entity);
- $query = $this->profile->query()->where(['user_id' => 1]);
- $this->assertEquals(1, $query->count(), 'Left non-matching row behind');
- $query = $this->profile->query()->where(['user_id' => 3]);
- $this->assertEquals(1, $query->count(), 'other records left behind');
- $user = new Entity(['id' => 3]);
- $this->assertTrue($association->cascadeDelete($user));
- $query = $this->profile->query()->where(['user_id' => 3]);
- $this->assertEquals(0, $query->count(), 'Matching record was deleted.');
- }
- /**
- * Tests cascading deletes on entities with null binding and foreign key.
- */
- public function testCascadeDeleteNullBindingNullForeign()
- {
- $Articles = $this->getTableLocator()->get('Articles');
- $Authors = $this->getTableLocator()->get('NullableAuthors');
- $config = [
- 'dependent' => true,
- 'sourceTable' => $Authors,
- 'targetTable' => $Articles,
- 'bindingKey' => 'author_id',
- 'foreignKey' => 'author_id',
- 'cascadeCallbacks' => false,
- ];
- $association = $Authors->hasOne('Articles', $config);
- // create article with null foreign key
- $entity = new Entity(['author_id' => null, 'title' => 'this has no author', 'body' => 'I am abandoned', 'published' => 'N']);
- $Articles->save($entity);
- // get author with null binding key
- $entity = $Authors->get(2, ['contain' => 'Articles']);
- $this->assertNull($entity->article);
- $this->assertTrue($association->cascadeDelete($entity));
- $query = $Articles->query();
- $this->assertSame(4, $query->count(), 'No articles should be deleted');
- }
- /**
- * Test cascading delete with has one.
- *
- * @return void
- */
- public function testCascadeDeleteCallbacks()
- {
- $config = [
- 'dependent' => true,
- 'sourceTable' => $this->user,
- 'targetTable' => $this->profile,
- 'conditions' => ['Profiles.is_active' => true],
- 'cascadeCallbacks' => true,
- ];
- $association = new HasOne('Profiles', $config);
- $user = new Entity(['id' => 1]);
- $this->assertTrue($association->cascadeDelete($user));
- $query = $this->profile->query()->where(['user_id' => 1]);
- $this->assertEquals(1, $query->count(), 'Left non-matching row behind');
- $query = $this->profile->query()->where(['user_id' => 3]);
- $this->assertEquals(1, $query->count(), 'other records left behind');
- $user = new Entity(['id' => 3]);
- $this->assertTrue($association->cascadeDelete($user));
- $query = $this->profile->query()->where(['user_id' => 3]);
- $this->assertEquals(0, $query->count(), 'Matching record was deleted.');
- }
- }
|