CakePHP 4.x: array_combine() Error When Saving Nested Associations

71 views Asked by At

Hello Stack Overflow community,

I'm working with CakePHP 4.x and facing an issue when trying to save nested associations. My application is processing orders, each containing OrderItems, and each OrderItem may have multiple OrderItemOptions. I'm using newEntity() methods to create these entities. However, when I attempt to save an Order entity that contains OrderItems with their OrderItemOptions, I encounter a ValueError related to array_combine().

The Error i'm getting

[ValueError] array_combine(): Argument #1 ($keys) and argument #2 ($values) must have the same number of elements

Simplified Api Objects: Object with 3 Options

"fields": [
    {
        "field_id": 2,
        "field_name": "Size",
        "field_type": 0,
        "string_value": null,
        "date_value": null,
        "file_value_url": null,
        "options": [
            {
                "option_id": 1,
                "qty": 50,
                "code": "S",
                "name": "Small",
                "sub_options": [],
                "sku": "STSU822C0081S",
                "vendor_sku": null,
                "dn_sku_id": "246924061_392440381"
            },
            {
                "option_id": 2,
                "qty": 50,
                "code": "M",
                "name": "Medium",
                "sub_options": [],
                "sku": "STSU822C0081M",
                "vendor_sku": null,
                "dn_sku_id": "246924061_392439396"
            },
            {
                "option_id": 4,
                "qty": 50,
                "code": "XL",
                "name": "X Large",
                "sub_options": [],
                "sku": "STSU822C0081X",
                "vendor_sku": null,
                "dn_sku_id": "246924061_392439371"
            }
        ]
    },
    {
        "field_id": 830881,
        "field_name": "Position",
        "field_type": 2,
        "string_value": null,
        "date_value": null,
        "file_value_url": null,
        "options": []
    },
    {
        "field_id": 822656,
        "field_name": "Druckposition",
        "field_type": 2,
        "string_value": null,
        "date_value": null,
        "file_value_url": null,
        "options": []
    }
],

Object without any Options

"fields": []

Object with 1 Option

"fields": [
    {
        "field_id": 2,
        "field_name": "Size",
        "field_type": 0,
        "string_value": null,
        "date_value": null,
        "file_value_url": null,
        "options": [
            {
                "option_id": 6214601,
                "qty": 150,
                "code": "100er",
                "name": "100er",
                "sub_options": [],
                "sku": "SBMU-C13",
                "vendor_sku": null,
                "dn_sku_id": "247765931_394789266"
            }
        ]
    },
    {
        "field_id": 830881,
        "field_name": "Position",
        "field_type": 2,
        "string_value": null,
        "date_value": null,
        "file_value_url": null,
        "options": []
    },
    {
        "field_id": 822656,
        "field_name": "Druckposition",
        "field_type": 2,
        "string_value": null,
        "date_value": null,
        "file_value_url": null,
        "options": []
    }
],

Here's a simplified version of my code: OrdersTable

public function saveOrders(array $orders): bool
{
    $newOrders = [];
    $defaultShippingService = $this->ShippingServices->find('all', [
        'conditions' => [
            'is_default' => true,
            'published' => true,
        ],
    ])->first();

    foreach ($orders['orders'] as $order) {
        $existingOrder = $this->find()->where(['order_id' => $order['order_id']])->first();

        if (!$existingOrder) {
            $assignedUser = $this->Users->find()->where(['deconetwork_user_id' => $order['assigned_to']['id']])->first();

            if ($assignedUser) {
                $company = $order['shipping_details']['company'] ?? $order['billing_details']['company'] ?? null;
                $details = $order['shipping_details'] ?? $order['billing_details'];

                $address = trim($details['street']);
                $firstName = trim($details['firstname']);
                $lastName = trim($details['lastname']);
                $street = trim($details['street']);
                $postcode = trim($details['postcode']);
                $city = trim($details['city']);
                $countryCode = trim($details['country_code']);
                $state = trim($details['state']);
                $email = $details['custom_fields'];

                if ($company) {
                    if (strlen($company) <= 35) {
                        $company_name1 = $company;
                        $company_name2 = null;
                    } else {
                        [$company_name1, $company_name2] = $this->splitCompanyName($company);
                    }
                } else {
                    $company_name1 = $firstName . ' ' . $lastName;
                    $company_name2 = null;
                }

                $addressParts = $this->splitAddress($street);

                $orderEntity = $this->newEntity([
                    'user_id' => $assignedUser->id,
                    'order_id' => $order['order_id'],
                    'customer_id' => $order['customer_id'],
                    'shipping_service_id' => $defaultShippingService ? $defaultShippingService->id : null,
                    'company' => $company,
                    'company_name1' => $company_name1,
                    'company_name2' => $company_name2,
                    'customer_reference_number1' => $order['order_id'],
                    'firstname' => $firstName,
                    'lastname' => $lastName,
                    'address' => $address,
                    'street' => $addressParts['street'],
                    'house_no' => $addressParts['house_no'],
                    'postcode' => $postcode,
                    'city' => $city,
                    'country_code' => $countryCode,
                    'state' => $state,
                    'email' => $this->getCustomerMail((array)$email),
                    'date_produced' => new FrozenTime($order['date_produced']),
                    'order_items' => [],
                ]);

                foreach ($order['order_lines'] as $orderItem) {
                    $orderItemEntity = $this->OrderItems->newEntity([
                        'quantity' => $orderItem['qty'],
                        'order_item_options' => [],
                    ]);

                    $productEntity = $this->OrderItems->Products->findOrCreate(
                        ['product_id' => $orderItem['product_id'] ?? 2],
                        function ($entity) use ($orderItem) {
                            $entity->product_code = $orderItem['product_code'] ?? null;
                            $entity->product_name = $orderItem['product_name'];
                            $entity->product_color = $orderItem['product_color']['name'] ?? null;
                        }
                    );

                    $orderItemEntity->product = $productEntity;

                    $orderItemOptions = [];
                    if (!empty($orderItem['fields'])) {
                        $field = $orderItem['fields'][0];

                        if ($field['field_id'] === 2 && !empty($field['options'])) {
                            foreach ($field['options'] as $option) {
                                $orderItemOption = $this->OrderItems->OrderItemOptions->newEntity([
                                    'option_type' => $field['field_name'],
                                    'option_value' => $option['code'],
                                    'quantity' => $option['qty'],
                                    'sku' => $option['sku']
                                ]);
                                $orderItemOptions[] = $orderItemOption;
                            }
                        }
                    }
                    if (!empty($orderItemOptions)) {
                        $orderItemEntity->order_item_options = $orderItemOptions;
                    }

                    $orderEntity->order_items[] = $orderItemEntity;
                }

                $newOrders[] = $orderEntity;
            }
        }
    }

    if (!empty($newOrders)) {
        #Log::write('error', 'Saving ' . print_r($newOrders, true));
        $savedOrders = $this->saveMany($newOrders, [
            'associated' => ['OrderItems.Products', 'OrderItems.OrderItemOptions']
        ]);

        if ($savedOrders) {
            return true;
        } else {
            foreach ($newOrders as $order) {
                Log::error('Order Error: ' . json_encode($order->getErrors()));
                foreach ($order->order_items as $item) {
                    Log::error('OrderItem Error: ' . json_encode($item->getErrors()));
                }
            }
        }
    }

    return false;
}

OrdersTable:

    public function initialize(array $config): void
    {
        parent::initialize($config);
        Syllable::setCacheDir(ROOT . DS . 'tmp' . DS . 'cache');

        $this->setTable('orders');
        $this->setDisplayField('firstname');
        $this->setPrimaryKey('id');

        $this->addBehavior('Timestamp');

        $this->belongsTo('Users', [
            'foreignKey' => 'user_id',
            'joinType' => 'INNER',
        ]);
        $this->belongsTo('ShippingServices', [
            'foreignKey' => 'shipping_service_id',
            'joinType' => 'INNER',
        ]);
        $this->hasMany('OrderItems', [
            'foreignKey' => 'order_id',
            'dependent' => true,
        ]);
    }

OrderItemsTable:

    public function initialize(array $config): void
    {
        parent::initialize($config);

        $this->setTable('order_items');
        $this->setDisplayField(['order_id', 'product_id']);
        $this->setPrimaryKey(['order_id', 'product_id']);

        $this->addBehavior('Timestamp');

        $this->belongsTo('Orders', [
            'foreignKey' => 'order_id',
            'joinType' => 'INNER',
        ]);
        $this->belongsTo('Products', [
            'foreignKey' => 'product_id',
            'joinType' => 'INNER',
        ]);
        $this->hasMany('OrderItemOptions', [
            'foreignKey' => 'order_item_id',
            'dependent' => true,
        ]);
    }

OrderItemOptionsTable:

    public function initialize(array $config): void
    {
        parent::initialize($config);

        $this->setTable('order_item_options');
        $this->setDisplayField('option_type');
        $this->setPrimaryKey(['order_item_id', 'order_id', 'product_id']);

        $this->addBehavior('Timestamp');

        $this->belongsTo('OrderItems', [
            'foreignKey' => 'order_item_id',
            'joinType' => 'INNER',
        ]);
    }

Log Stack Trace using Log::write

if (!empty($orderItemOptions)) {
  $orderItemEntity->order_item_options = $orderItemOptions;
  Log::write('error', 'Saving ' . print_r($orderItemEntity, true));
}
2024-01-05 06:31:47 error: Saving App\Model\Entity\OrderItem Object
(
    [quantity] => 150
    [order_item_options] => Array
        (
            [0] => App\Model\Entity\OrderItemOption Object
                (
                    [option_type] => Size
                    [option_value] => S
                    [quantity] => 50
                    [sku] => STSU822C0081S
                    [[new]] => 1
                    [[accessible]] => Array
                        (
                            [order_item_id] => 1
                            [option_type] => 1
                            [option_value] => 1
                            [quantity] => 1
                            [sku] => 1
                            [created] => 1
                            [modified] => 1
                            [order_item] => 1
                        )

                    [[dirty]] => Array
                        (
                            [option_type] => 1
                            [option_value] => 1
                            [quantity] => 1
                            [sku] => 1
                        )

                    [[original]] => Array
                        (
                        )

                    [[virtual]] => Array
                        (
                        )

                    [[hasErrors]] => 
                    [[errors]] => Array
                        (
                        )

                    [[invalid]] => Array
                        (
                        )

                    [[repository]] => OrderItemOptions
                )

            [1] => App\Model\Entity\OrderItemOption Object
                (
                    [option_type] => Size
                    [option_value] => M
                    [quantity] => 50
                    [sku] => STSU822C0081M
                    [[new]] => 1
                    [[accessible]] => Array
                        (
                            [order_item_id] => 1
                            [option_type] => 1
                            [option_value] => 1
                            [quantity] => 1
                            [sku] => 1
                            [created] => 1
                            [modified] => 1
                            [order_item] => 1
                        )

                    [[dirty]] => Array
                        (
                            [option_type] => 1
                            [option_value] => 1
                            [quantity] => 1
                            [sku] => 1
                        )

                    [[original]] => Array
                        (
                        )

                    [[virtual]] => Array
                        (
                        )

                    [[hasErrors]] => 
                    [[errors]] => Array
                        (
                        )

                    [[invalid]] => Array
                        (
                        )

                    [[repository]] => OrderItemOptions
                )

            [2] => App\Model\Entity\OrderItemOption Object
                (
                    [option_type] => Size
                    [option_value] => XL
                    [quantity] => 50
                    [sku] => STSU822C0081X
                    [[new]] => 1
                    [[accessible]] => Array
                        (
                            [order_item_id] => 1
                            [option_type] => 1
                            [option_value] => 1
                            [quantity] => 1
                            [sku] => 1
                            [created] => 1
                            [modified] => 1
                            [order_item] => 1
                        )

                    [[dirty]] => Array
                        (
                            [option_type] => 1
                            [option_value] => 1
                            [quantity] => 1
                            [sku] => 1
                        )

                    [[original]] => Array
                        (
                        )

                    [[virtual]] => Array
                        (
                        )

                    [[hasErrors]] => 
                    [[errors]] => Array
                        (
                        )

                    [[invalid]] => Array
                        (
                        )

                    [[repository]] => OrderItemOptions
                )

        )

    [product] => App\Model\Entity\Product Object
        (
            [id] => 27
            [product_id] => 246924061
            [product_code] => STSU822-B
            [product_name] => STSU822 Cruiser Iconic Unisex Hoodie
            [product_color] => British Khaki
            [created] => Cake\I18n\FrozenTime Object
                (
                    [date] => 2024-01-05 05:17:16.000000
                    [timezone_type] => 3
                    [timezone] => Europe/Berlin
                )

            [modified] => Cake\I18n\FrozenTime Object
                (
                    [date] => 2024-01-05 05:17:16.000000
                    [timezone_type] => 3
                    [timezone] => Europe/Berlin
                )

            [[new]] => 
            [[accessible]] => Array
                (
                    [product_id] => 1
                    [product_code] => 1
                    [product_name] => 1
                    [product_color] => 1
                    [created] => 1
                    [modified] => 1
                    [order_items] => 1
                )

            [[dirty]] => Array
                (
                )

            [[original]] => Array
                (
                )

            [[virtual]] => Array
                (
                )

            [[hasErrors]] => 
            [[errors]] => Array
                (
                )

            [[invalid]] => Array
                (
                )

            [[repository]] => Products
        )

    [[new]] => 1
    [[accessible]] => Array
        (
            [order_id] => 1
            [product_id] => 1
            [quantity] => 1
            [created] => 1
            [modified] => 1
            [order] => 1
            [product] => 1
            [order_item_options] => 1
        )

    [[dirty]] => Array
        (
            [quantity] => 1
            [order_item_options] => 1
            [product] => 1
        )

    [[original]] => Array
        (
            [order_item_options] => Array
                (
                )

        )

    [[virtual]] => Array
        (
        )

    [[hasErrors]] => 
    [[errors]] => Array
        (
        )

    [[invalid]] => Array
        (
        )

    [[repository]] => OrderItems
)

2024-01-05 06:31:47 error: Saving App\Model\Entity\OrderItem Object
(
    [quantity] => 150
    [order_item_options] => Array
        (
            [0] => App\Model\Entity\OrderItemOption Object
                (
                    [option_type] => Size
                    [option_value] => 100er
                    [quantity] => 150
                    [sku] => SBMU-C13
                    [[new]] => 1
                    [[accessible]] => Array
                        (
                            [order_item_id] => 1
                            [option_type] => 1
                            [option_value] => 1
                            [quantity] => 1
                            [sku] => 1
                            [created] => 1
                            [modified] => 1
                            [order_item] => 1
                        )

                    [[dirty]] => Array
                        (
                            [option_type] => 1
                            [option_value] => 1
                            [quantity] => 1
                            [sku] => 1
                        )

                    [[original]] => Array
                        (
                        )

                    [[virtual]] => Array
                        (
                        )

                    [[hasErrors]] => 
                    [[errors]] => Array
                        (
                        )

                    [[invalid]] => Array
                        (
                        )

                    [[repository]] => OrderItemOptions
                )

        )

    [product] => App\Model\Entity\Product Object
        (
            [id] => 29
            [product_id] => 247765931
            [product_code] => SBMU-C
            [product_name] => Siebdruck / Mit Unterdruck
            [product_color] => 2–farbig
            [created] => Cake\I18n\FrozenTime Object
                (
                    [date] => 2024-01-05 05:17:16.000000
                    [timezone_type] => 3
                    [timezone] => Europe/Berlin
                )

            [modified] => Cake\I18n\FrozenTime Object
                (
                    [date] => 2024-01-05 05:17:16.000000
                    [timezone_type] => 3
                    [timezone] => Europe/Berlin
                )

            [[new]] => 
            [[accessible]] => Array
                (
                    [product_id] => 1
                    [product_code] => 1
                    [product_name] => 1
                    [product_color] => 1
                    [created] => 1
                    [modified] => 1
                    [order_items] => 1
                )

            [[dirty]] => Array
                (
                )

            [[original]] => Array
                (
                )

            [[virtual]] => Array
                (
                )

            [[hasErrors]] => 
            [[errors]] => Array
                (
                )

            [[invalid]] => Array
                (
                )

            [[repository]] => Products
        )

    [[new]] => 1
    [[accessible]] => Array
        (
            [order_id] => 1
            [product_id] => 1
            [quantity] => 1
            [created] => 1
            [modified] => 1
            [order] => 1
            [product] => 1
            [order_item_options] => 1
        )

    [[dirty]] => Array
        (
            [quantity] => 1
            [order_item_options] => 1
            [product] => 1
        )

    [[original]] => Array
        (
            [order_item_options] => Array
                (
                )

        )

    [[virtual]] => Array
        (
        )

    [[hasErrors]] => 
    [[errors]] => Array
        (
        )

    [[invalid]] => Array
        (
        )

    [[repository]] => OrderItems
)

2024-01-05 06:31:47 error: [ValueError] array_combine(): Argument #1 ($keys) and argument #2 ($values) must have the same number of elements in /vendor/cakephp/cakephp/src/ORM/Association/HasMany.php on line 170
Stack Trace:
- /vendor/cakephp/cakephp/src/ORM/Association/HasMany.php:170
- /vendor/cakephp/cakephp/src/ORM/AssociationCollection.php:315
- /vendor/cakephp/cakephp/src/ORM/AssociationCollection.php:285
- /vendor/cakephp/cakephp/src/ORM/AssociationCollection.php:245
- /vendor/cakephp/cakephp/src/ORM/Table.php:2074
- /vendor/cakephp/cakephp/src/ORM/Table.php:2047
- /vendor/cakephp/cakephp/src/ORM/Table.php:1940
- /vendor/cakephp/cakephp/src/ORM/Table.php:1582
- /vendor/cakephp/cakephp/src/Database/Connection.php:896
- /vendor/cakephp/cakephp/src/ORM/Table.php:1583
- /vendor/cakephp/cakephp/src/ORM/Table.php:1941
- /vendor/cakephp/cakephp/src/ORM/Association/HasMany.php:228
- /vendor/cakephp/cakephp/src/ORM/Association/HasMany.php:185
- /vendor/cakephp/cakephp/src/ORM/AssociationCollection.php:315
- /vendor/cakephp/cakephp/src/ORM/AssociationCollection.php:285
- /vendor/cakephp/cakephp/src/ORM/AssociationCollection.php:245
- /vendor/cakephp/cakephp/src/ORM/Table.php:2074
- /vendor/cakephp/cakephp/src/ORM/Table.php:2047
- /vendor/cakephp/cakephp/src/ORM/Table.php:1940
- /vendor/cakephp/cakephp/src/ORM/Table.php:1582
- /vendor/cakephp/cakephp/src/Database/Connection.php:896
- /vendor/cakephp/cakephp/src/ORM/Table.php:1583
- /vendor/cakephp/cakephp/src/ORM/Table.php:1941
- /vendor/cakephp/cakephp/src/ORM/Table.php:2320
- /vendor/cakephp/cakephp/src/Database/Connection.php:896
- /vendor/cakephp/cakephp/src/ORM/Table.php:2326
- /vendor/cakephp/cakephp/src/ORM/Table.php:2254
- /src/Model/Table/OrdersTable.php:370
- /src/Controller/OrdersController.php:258
- /vendor/cakephp/cakephp/src/Controller/Controller.php:560
- /vendor/cakephp/cakephp/src/Controller/ControllerFactory.php:140
- /vendor/cakephp/cakephp/src/Controller/ControllerFactory.php:115
- /vendor/cakephp/cakephp/src/Http/BaseApplication.php:325
- /vendor/cakephp/cakephp/src/Http/Runner.php:86
- /vendor/cakephp/authorization/src/Middleware/RequestAuthorizationMiddleware.php:110
- /vendor/cakephp/cakephp/src/Http/Runner.php:82
- /vendor/cakephp/authorization/src/Middleware/AuthorizationMiddleware.php:129
- /vendor/cakephp/cakephp/src/Http/Runner.php:82
- /vendor/cakephp/authentication/src/Middleware/AuthenticationMiddleware.php:124
- /vendor/cakephp/cakephp/src/Http/Runner.php:82
- /vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php:176
- /vendor/cakephp/cakephp/src/Http/Runner.php:82
- /vendor/cakephp/cakephp/src/Http/Middleware/BodyParserMiddleware.php:157
- /vendor/cakephp/cakephp/src/Http/Runner.php:82
- /vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php:189
- /vendor/cakephp/cakephp/src/Http/Runner.php:82
- /vendor/cakephp/cakephp/src/Routing/Middleware/AssetMiddleware.php:68
- /vendor/cakephp/cakephp/src/Http/Runner.php:82
- /vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php:149
- /vendor/cakephp/cakephp/src/Http/Runner.php:82
- /vendor/cakephp/debug_kit/src/Middleware/DebugKitMiddleware.php:60
- /vendor/cakephp/cakephp/src/Http/Runner.php:82
- /vendor/cakephp/cakephp/src/Http/Runner.php:67
- /vendor/cakephp/cakephp/src/Http/Server.php:99
- /webroot/index.php:40
- [main]:

Request URL: /api/get/orders
Referer URL: https://local.project.com/
Client IP: 127.0.0.1

/src/Model/Table/OrdersTable.php:370

'associated' => ['OrderItems.Products', 'OrderItems.OrderItemOptions']

Additional Context: An important observation is that if I comment out the line where order_item_options are assigned to $orderItemEntity, like so:

// $orderItemEntity->order_item_options = $orderItemOptions;

the Order and OrderItems get saved successfully without any errors. This leads me to believe that the issue specifically lies in how the OrderItemOptions are being handled or associated with OrderItem.

Any insights into why assigning order_item_options is causing the array_combine() error would be extremely helpful.

0

There are 0 answers