laravel-nestedset icon indicating copy to clipboard operation
laravel-nestedset copied to clipboard

create() with children attribute throwing error

Open murilozilli opened this issue 7 years ago • 7 comments

I am sending the following array to the create()

array(3) {
  ["name"]=>
  string(4) "cat1"
  ["company_id"]=>
  int(1)
  ["children"]=> array(1) {
    array(2) {
      ["name"]=>
      string(4) "cat2"
      ["company_id"]=>
      int(1)
    }
  }
}

Those categories don't exist in my DB. But I do have other categories already. I want to create a new node being a root and its children.

It is creating only the first one cat1 without setting _lft and _rgt(both null in DB) attributes then it is throwing an Exception

Since the _lft and _rgt are being set with null on my parent category I get the following errors: Node must exists. at /vendor/kalnoy/nestedset/src/NodeTrait.php:1155

More of the stack trace:

#0 /vendor/kalnoy/nestedset/src/NodeTrait.php(435): App\Model\Category->assertNodeExists(Object(App\Model\Category))
#1 /vendor/kalnoy/nestedset/src/NodeTrait.php(412): App\Model\Category->appendOrPrependTo(Object(App\Model\Category))
#2 /vendor/kalnoy/nestedset/src/NodeTrait.php(766): App\Model\Category->appendToNode(Object(App\Model\Category))
#3 /vendor/kalnoy/nestedset/src/NodeTrait.php(775): App\Model\Category::create(Array, Object(App\Model\Category))
#4 App\Model\Category::create(Array)

Looking at the code I think there is a bug on the create() function creating the root node, like a missing call to makeRoot() along with maybe some bug on the events for the children updates.

I solved the problem by directly calling action methods inside the create() function like this on NodeTrait(765):

if ($parent) {
    $instance->appendToNode($parent);
    $instance->actionAppendOrPrepend($parent);
} else if ($instance->getParentId() === null) {
    $instance->actionRoot();
}

I know this isn`t the correct way of fixing it.

I also think it is important to mention I am partially using Laravel, I am just using Eloquent.

So my libs regarding illuminate are:

"illuminate/database": "^5.3",
"illuminate/support": "5.2.* | 5.3.* | 5.4.*",
"illuminate/events": "5.2.* | 5.3.* | 5.4.*",
"illuminate/contracts": "5.4.*",
"illuminate/container": "5.4.*",
"illuminate/pagination": "5.4.*"

Maybe this is due to a lack of dependency statement on composer.json

murilozilli avatar Apr 04 '17 14:04 murilozilli

If you're using only Eloquent, you need it to generate events properly and for this, dispatcher should be set up

lazychaser avatar Apr 08 '17 05:04 lazychaser

I have the events(which dispatcher is part of) lib like I mentioned in the end on the copy of part of my composer.json.

murilozilli avatar Apr 10 '17 12:04 murilozilli

Yes, but you need to set dispatcher on Eloquent

lazychaser avatar Apr 10 '17 14:04 lazychaser

Well, I have this slice of code here which I call on my index.php file to boot the eloquent:

$capsule = new Illuminate\Database\Capsule\Manager();
$capsule->addConnection($container['eloquent']);
$capsule->setAsGlobal();
$capsule->bootEloquent();

on bootEloquent() there is this:

// If we have an event dispatcher instance, we will go ahead and register it
// with the Eloquent ORM, allowing for model callbacks while creating and
// updating "model" instances; however, it is not necessary to operate.
if ($dispatcher = $this->getEventDispatcher()) {
    Eloquent::setEventDispatcher($dispatcher);
}

murilozilli avatar Apr 10 '17 17:04 murilozilli

I had a similar problem; I'm seeding a table (Category) with complex nested data using Phinx. In the seeder file (CategoriesSeeder.php - which extends AbstractSeeder) I have an init method. In this very method I initialize a new Capsule (use Illuminate\Database\Capsule\Manager as Capsule;, add it to a new connection, set as global... as @murilozilli did in there. And then in the run method I set the model's event dispatcher like so;

Category::setEventDispatcher($this->capsule->getEventDispatcher());

Bam, events on the observer (CategoryObserver) working now. Hope this helps.

EDIT: Right after the line that sets the model's event dispatcher that line exists (the data below demonstration only) and works as expected;

Category::rebuildTree([
    [
        'name' => 'Category 1',
        // other fields of Category instance for "Category 1" (if any)

        'children' => [
            'name' => 'Subcategory 1 of Category 1'
            // other fields of Category instance for "Subcategory 1 of Category 1" (if any)

            // children of "Subcategory 1 of Category 1" (if any)
       ]
    ],
    [
        'name' => 'Category 2',
        // other fields of Category instance for "Category 2" (if any)

        // children of "Category 2" (if any)
    ],
    //
]);

ozanmuyes avatar Nov 14 '19 14:11 ozanmuyes

Hi,

i have the same issue, tried the solution above to get an eventdispatcher instance, but: $capsule->getEventDispatcher() returns NULL.

I use eloquent outside laravel and initialize it exactly the way murilozilli does. Just the nestedset-trait does not work, it prints the same error as stated above =/

pixxelfactory avatar Jun 30 '20 14:06 pixxelfactory

I know this is an old question but here is my solution. I'm working with eloquent outside laravel and initialize capsule in this way.

// Create new IoC Container instance
$container = new Container;
$container->bind('app', $container);

//  Capsule
$capsule = new Capsule;

$capsule->addConnection([
    'driver' =>'mysql',
    'host' => $dbhost,
    'database' => $dbname,
    'username' => $dbuname,
    'password' => $dbpass,
    'charset' => 'utf8',
    'collation' => 'utf8_unicode_ci',
    'prefix' => $prefix."_",
]);

$capsule->setEventDispatcher(new Dispatcher);

// Make this Capsule instance available globally via static methods... (optional)
$capsule->setAsGlobal();

// Eloquent
$capsule->bootEloquent();

$container->instance("capsule", $capsule);

Without this line,

$capsule->setEventDispatcher(new Dispatcher);

the code does not work.

Best regards

drjoju avatar Jul 11 '21 13:07 drjoju