From d06179a332613a810aed5efafabee030792f8c80 Mon Sep 17 00:00:00 2001 From: Ruslan Stelmachenko Date: Thu, 27 Jul 2017 05:10:53 +0300 Subject: [PATCH 1/3] Honor primary_key option in $has_many when eager loading. Fixes #366. --- lib/Relationship.php | 8 ++++---- test/RelationshipTest.php | 23 ++++++++++++++++++++--- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/lib/Relationship.php b/lib/Relationship.php index 9846b44a4..92fe3fabc 100644 --- a/lib/Relationship.php +++ b/lib/Relationship.php @@ -291,7 +291,7 @@ protected function set_class_name($class_name) if (!has_absolute_namespace($class_name) && isset($this->options['namespace'])) { $class_name = $this->options['namespace'].'\\'.$class_name; } - + $reflection = Reflections::instance()->add($class_name)->get($class_name); if (!$reflection->isSubClassOf('ActiveRecord\\Model')) @@ -507,7 +507,7 @@ public function load(Model $model) $fk = $this->foreign_key; $this->set_keys($this->get_table()->class->getName(), true); - + $class = $this->class_name; $relation = $class::table()->get_relationship($this->through); $through_table = $relation->get_table(); @@ -604,7 +604,7 @@ public function create_association(Model $model, $attributes=array(), $guard_att public function load_eagerly($models=array(), $attributes=array(), $includes, Table $table) { $this->set_keys($table->class->name); - $this->query_and_attach_related_models_eagerly($table,$models,$attributes,$includes,$this->foreign_key, $table->pk); + $this->query_and_attach_related_models_eagerly($table,$models,$attributes,$includes,$this->foreign_key, $this->primary_key); } } @@ -727,4 +727,4 @@ public function load_eagerly($models=array(), $attributes, $includes, Table $tab { $this->query_and_attach_related_models_eagerly($table,$models,$attributes,$includes, $this->primary_key,$this->foreign_key); } -} \ No newline at end of file +} diff --git a/test/RelationshipTest.php b/test/RelationshipTest.php index 957016c65..871adb01f 100644 --- a/test/RelationshipTest.php +++ b/test/RelationshipTest.php @@ -23,7 +23,7 @@ public function set_up($connection_name=null) Venue::$has_one = array(); Employee::$has_one = array(array('position')); Host::$has_many = array(array('events', 'order' => 'id asc')); - + foreach ($this->relationship_names as $name) { if (preg_match("/$name/", $this->getName(), $match)) @@ -80,7 +80,7 @@ public function test_has_many_basic() { $this->assert_default_has_many($this->get_relationship()); } - + public function test_eager_load_with_empty_nested_includes() { $conditions['include'] = array('events'=>array()); @@ -107,7 +107,7 @@ public function test_gh_256_eager_loading_three_levels_deep() $this->assertEquals('Yeah Yeah Yeahs',$bill_events[0]->title); } - + /** * @expectedException ActiveRecord\RelationshipException */ @@ -423,6 +423,23 @@ public function test_has_many_with_explicit_keys() Author::$has_many = array(array('explicit_books', 'class_name' => 'Book', 'primary_key' => 'parent_author_id', 'foreign_key' => 'secondary_author_id')); $author = Author::find(4); + $this->assert_equals(2, count($author->explicit_books)); + + foreach ($author->explicit_books as $book) + $this->assert_equals($book->secondary_author_id, $author->parent_author_id); + + $this->assert_true(strpos(ActiveRecord\Table::load('Book')->last_sql, "secondary_author_id") !== false); + Author::$has_many = $old; + } + + public function test_has_many_with_explicit_keys_and_eager_loading() + { + $old = Author::$has_many; + Author::$has_many = array(array('explicit_books', 'class_name' => 'Book', 'primary_key' => 'parent_author_id', 'foreign_key' => 'secondary_author_id')); + $author = Author::find(4, array('include' => 'explicit_books')); + + $this->assert_equals(2, count($author->explicit_books)); + foreach ($author->explicit_books as $book) $this->assert_equals($book->secondary_author_id, $author->parent_author_id); From aa7f78a9a99f3e2028a5aef04d972c2dae0d31a4 Mon Sep 17 00:00:00 2001 From: Ruslan Stelmachenko Date: Thu, 27 Jul 2017 05:15:13 +0300 Subject: [PATCH 2/3] Honor primary_key option in $belongs_to. Fixes #364. --- lib/Relationship.php | 6 ++++++ test/RelationshipTest.php | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/Relationship.php b/lib/Relationship.php index 92fe3fabc..61b80ea5e 100644 --- a/lib/Relationship.php +++ b/lib/Relationship.php @@ -685,6 +685,9 @@ public function load(Model $model) */ class BelongsTo extends AbstractRelationship { + + static protected $valid_association_options = array('primary_key'); + public function __construct($options=array()) { parent::__construct($options); @@ -695,6 +698,9 @@ public function __construct($options=array()) //infer from class_name if (!$this->foreign_key) $this->foreign_key = array(Inflector::instance()->keyify($this->class_name)); + + if (!isset($this->primary_key) && isset($this->options['primary_key'])) + $this->primary_key = is_array($this->options['primary_key']) ? $this->options['primary_key'] : array($this->options['primary_key']); } public function __get($name) diff --git a/test/RelationshipTest.php b/test/RelationshipTest.php index 871adb01f..ccea4073a 100644 --- a/test/RelationshipTest.php +++ b/test/RelationshipTest.php @@ -165,6 +165,19 @@ public function test_belongs_to_with_explicit_foreign_key() Book::$belongs_to = $old; } + public function test_belongs_to_with_explicit_primary_key() + { + $old = Book::$belongs_to; + Book::$belongs_to = array(array('explicit_author', 'class_name' => 'Author', 'primary_key' => 'parent_author_id')); + + $book = Book::find(1); + $this->assert_equals(1, $book->author_id); + $this->assert_equals(1, $book->explicit_author->parent_author_id); + $this->assert_equals(3, $book->explicit_author->author_id); + + Book::$belongs_to = $old; + } + public function test_belongs_to_with_select() { Event::$belongs_to[0]['select'] = 'id, city'; From a7432fb5e2c73d9fc4e61e60ce83d9b2788c1c64 Mon Sep 17 00:00:00 2001 From: Ruslan Stelmachenko Date: Thu, 9 Jan 2020 07:17:25 +0200 Subject: [PATCH 3/3] Fix eager loading of nested relationships Before this change, some eager loaded nested relationships were wiped out when some first-level relationships were the same model object. For example, when 2 `Post`s have the same `Author` and you load posts with option `'include' => ['author' => ['profile']]`, then first loaded post will have eager loaded author with eager loaded profile, while the second post will have eager loaded author (cloned), but without eager loaded profile. `clone` method just doesn't clone the relationships of cloned author. This leads to a serious performance degradation. Because you expect all relationships are eagerly loaded when traverse them in the code, while in reality only some of them are loaded, and others generate many unnecessary SQL queries to load them on-the-fly. After this change, nested relationships will be cloned recursively instead of wiping them out completely. This leads to expected behavior, when all eagerly loaded relationships will be preserved and fixes the performance degradation. --- lib/Model.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Model.php b/lib/Model.php index 99667b41f..03a01f2af 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -1291,7 +1291,9 @@ public function reload() public function __clone() { - $this->__relationships = array(); + $this->__relationships = array_map(function($rel) { + return clone($rel); + }, $this->__relationships); $this->reset_dirty(); return $this; }