Hi!

I'm using current commerce 2.x.dev for online store development. It's first project with Commerce 2 for me.

When i started to work on products import, i found that Feeds module does not stable, and i decided to write custom solution for data import (Batch/Queue API data import from CSV/XML sources).

So, at this moment i cannot find any information about correct product entities creation via code. I explored Drupal Commerce documentation section: http://docs.drupalcommerce.org/v2/product/products.html but it contains only UI instructions for manual products management.

I think that short instruction for working from code with products / orders entities will be very helpful for developers, especially for developers, who starts working with commerce 2 and have some experience with 7.x commerce.

Comments

sun-fire created an issue. See original summary.

jpdaut’s picture

I completely second this, I need it too.

mbreden’s picture

I will try to explain how to programmatically add stores, products, etc.

Creating a store

It all starts at the store. Products, orders, etc all reference a store. For most sites, there will probably only be a single store. Orders belong to a single store, whereas products can belong to many stores.

// The store type. Default is 'online'.
$type = 'online';
 
// The user id the store belongs to.
$uid = 1;

// The store's name.
$name = 'My Store';

// Store's email address.
$mail = 'admin@example.com';

// The country code.
$country = 'US';

// The store's address.
$address = [
  'country_code' => $country,
  'address_line1' => '123 Street Drive',
  'locality' => 'Beverly Hills',
  'administrative_area' => 'CA',
  'postal_code' => '90210',
];

// The currency code.
$currency = 'USD';

// If needed, this will import the currency.
$currency_importer = \Drupal::service('commerce_price.currency_importer');
$currency_importer->import($currency);

$store = \Drupal\commerce_store\Entity\Store::create([
  'type' => $type,
  'uid' => $uid,
  'name' => $name,
  'mail' => $mail,
  'address' => $address,
  'default_currency' => $currency,
  'billing_countries' => [
    $country,
  ],
]);

// If needed, this sets the store as the default store.
$store_storage = \Drupal::service('entity_type.manager')->getStorage('commerce_store');
$store_storage->markAsDefault($store);

// Finally, save the store.
$store->save();


Loading a store

If you already have a store, you can load by it's id:

$store = \Drupal\commerce_store\Entity\Store::load(1);


Creating a product variation

Now that we have a store, we can create products and their variations. Every product needs a variation first.

// The price of the variation.
$price = new \Drupal\commerce_price\Price('24.99', 'USD');

$variation = \Drupal\commerce_product\Entity\ProductVariation::create([
  'type' => 'default', // The default variation type is 'default'.
  'sku' => 'test-product-01', // The variation sku.
  'status' => 1, // The product status. 0 for disabled, 1 for enabled.
  'price' => $price,
]);
$variation->save();


Creating a custom product variation type and attributes

If you want to make custom attributes for your products, you will most likely want to use a custom variation type and add the custom attributes & values to it.

// Create the new variation type.
$variationType = \Drupal\commerce_product\Entity\ProductVariationType::create([
  'status' => 1, // Status, 0 for disabled, 1 for enabled.
  'id' => 'variation_type_with_color',
  'label' => 'Variation Type With Color',
  'orderItemType' => 'default', // Order item type to reference. Default is 'default'.
  'generateTitle' => TRUE, // True to generate titles based off of attribute values.
]);
$variationType->save();

// The actual attribute we want to make. Super creative 'color' being used as an example.
$colorAttribute = \Drupal\commerce_product\Entity\ProductAttribute::create([
  'id' => 'color',
  'label' => 'color',
]);
$colorAttribute->save();

// The service that adds the attribute to the variation type.
$fieldManager = \Drupal::service('commerce_product.attribute_field_manager');
$fieldManager->createField($colorAttribute, $variationType->id());

// Making the individual attribute values for 'color'.
$blue = \Drupal\commerce_product\Entity\ProductAttributeValue::create([
  'attribute' => 'color', // The attribute we are referencing.
  'name' => 'blue', // Name of the actual value.
]);
$blue->save();

$red = \Drupal\commerce_product\Entity\ProductAttributeValue::create([
  'attribute' => 'color', // The attribute we are referencing.
  'name' => 'red', // Name of the actual value.
]);
$red->save();

// Now we make the variation like we did above, except we also have to state the attribute for color.
$variation = \Drupal\commerce_product\Entity\ProductVariation::create([
  'type' => 'variation_type_with_color', // The default variation type is 'default'.
  'sku' => 'test-product-01', // The variation sku.
  'status' => 1, // The product status. 0 for disabled, 1 for enabled.
  'price' => $price,
  'attribute_color' => $red, // Set the attribute_color to the value entity, can use id as well.
]);
$variation->save();

Creating a product

Next up, we need to make the product itself.

$product = \Drupal\commerce_product\Entity\Product::create([
  'uid' => 1, // The user id that created the product.
  'type' => 'default', // Just like variation, the default product type is 'default'.
  'title' => 'My product',
  'stores' => [$store], // The store we created/loaded above.
  'variations' => [$variation], // The variation we created above.
]);
$product->save();

// You can also add a variation to a product using the addVariation() method.
$product->addVariation($variation);
$product->save();


Creating custom product types

To make a custom product type that references a specific variation type, you do this.

$productType = \Drupal\commerce_product\Entity\ProductType::create([
  'id' => 'with_color_variations',
  'label' => 'Product Type with Color Variations',
  'status' => 1, // 0 for disabled, 1 for enabled
  'description' => 'This is the description of the product', // Optional
  'variationType' => 'variation_type_with_color', // The variation type we want to reference for this
  'injectVariationFields' => TRUE, // Optional - defaults to true
]);
$productType->save();

// These three functions must be called to add the appropriate fields to the type
commerce_product_add_variations_field($productType);
commerce_product_add_stores_field($productType);
commerce_product_add_body_field($productType);

// Now if you want to make a product with this type, you just set the type like this
$product = \Drupal\commerce_product\Entity\Product::create([
  'uid' => 1,
  'type' => 'with_color_variations', // Our newly created product type
  'title' => 'My product',
  'stores' => [$store],
  'variations' => [$variation],
]);
$product->save();

Creating orders

Now that we have a store, product variations and products, we can add orders and order items.

$order_item = \Drupal\commerce_order\Entity\OrderItem::create([
  'type' => 'product_variation', // The default order item type is 'product_variation' which references the product variation purchasable entity type.
  'purchased_entity' => $variation, // The product variation we created above.
  'quantity' => 2,
  'unit_price' => $variation->getPrice(),
]);
$order_item->save();

// You can set the quantity with setQuantity.
$order_item->setQuantity('1');
$order_item->save();

// You can also set the price with setUnitPrice.
$unit_price = new  \Drupal\commerce_price\Price('9.99', 'USD');
$order_item->setUnitPrice($unit_price);
$order_item->save();

// Next we create the billing profile.
$profile = \Drupal\profile\Entity\Profile::create([
  'type' => 'customer',
  'uid' => 1, // The user id that the billing profile belongs to.
]);
$profile->save();

// Next, we create the order.
$order = \Drupal\commerce_order\Entity\Order::create([
  'type' => 'default', // The default order type is 'default'.
  'state' => 'draft', // The states for the default order type are draft, completed, canceled
  'mail' => 'user@example.com', // The email address for the order.
  'uid' => 1, // The user id the order belongs to.
  'ip_address' => '127.0.0.1', // The ip address the user ordered from.
  'order_number' => '6', // Sets the order number. If left out, will use the order's entity ID.
  'billing_profile' => $profile, // The profile we just created.
  'store_id' => $store->id(), // The store we created above.
  'order_items' => [$order_item], // The order item we just created.
  'placed' => time(), // The time the order was placed.
]);
$order->save();

// You can also add order items to an order.
$order->addItem($order_item);
$order->save();



If I missed anything I will update this post. Hope it helps!

jpdaut’s picture

This is great thank you so much! I went as far as the products, everything works, I have a couple questions.

1/ The variation title is not carried through into the product, it says N/A on the product edit form. Why is that? In the docs it says "Variations do not have labels or titles". But when you add a variation from a product variation type, the title is set to the attribute value chosen. So, this is confusing to me at the moment. Can you clarify?

2/ Your example demonstrates a variation which is a regular fieldset on the product. But my product type uses a variation type, which has an attribute with several possible values on it. I'd like to create the product with all the variations. Could you explain how to programmatically retrieve the variation type, then each of its attribute value, to create the variations with their Price and SKU? This would be most helpful. Thank you.

mbreden’s picture

I updated my post above to include "Creating a custom product variation type and attributes".

For 1)
If you take a look at my updated part, you'll see 'generateTitle' => TRUE, under the variation type. If this is set to true (as is on the default variation type) then the code will automatically generate titles for each variation, basing it on the product name itself and the attribute names. (IE "Shirt - Blue, Large").

If generateTitle is set to false, then the title given to it will stay.

For 2)
I hope I mostly answered this question with the updated code. If you want to retrieve a list of values for a product variation, you can call
$variation->getAttributeValues()

That helpful?

mbreden’s picture

I've create an issue to follow up and add these to the official docs so it's always available, with more examples and information.

https://www.drupal.org/node/2817491

If you have any more questions you can keep asking them here and I'll try and stay on top of it.

jpdaut’s picture

Yes this really helps. I'm only missing one step : that of creating the product type that has the variation type with color, so that the product created can be of that type. Could you please add that? It would be absolutely great. Thanks.

mbreden’s picture

Ahh, I missed that. OK I added it up there underneath custom products. Was there anything else, or does that mostly cover it now?

jpdaut’s picture

Works perfectly. One more thing would be to show how to create the product with not just the red attribute variation like now, but also with the blue attribute variation (not created in the example currently). So, create the product with both variations in one call, how would the code look to do that? This would be very useful.

Finally, for this great tutorial and the API docs, could you show boilerplate code to load things, like load all variation types, all product types. Also, how to load all products of a specific product type. And finally finally how to add a variation to a product. I hope this is not too much asking! Thanks!

sun-fire’s picture

Thank you so much! This really helps in development!

mbreden’s picture

The official docs now have a 'recipe' section, check it out: http://docs.drupalcommerce.org/v2/recipes/

If you copy/paste the code from top to bottom, it will all work. It also shows how to load entities, and how to add multiple attributes to a variation type, and multiple variations to the same product.

Also, if you want to know how to load an entity by it's property (like it's type), you can use this:

$storage = \Drupal::entityTypeManager()->getStorage('commerce_product');
$products = $storage->loadByProperties(['type' => 'my_custom_product_type']);
bojanz’s picture

Status: Active » Fixed

We now have docs, the repository is at https://github.com/drupalcommerce/commerce-docs for you to send pull requests or open issues against. Thanks, everyone.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

suresh.senthatti’s picture

Priority: Normal » Critical

Could anyone knows, how to do the product insert with multi language?

Parthvi’s picture

Yes, how to do the product insert with multi language? and How to edit existing product?

Anybody please help!

Parthvi’s picture

I am looking for the product delete code also.

I tried \Drupal\commerce_product\Entity\Product::delete($product_id); but not working.

Is there any way to delete multiple products?

Thanks in advance.

sun-fire’s picture

Hi!

About #15 - it's a fairly simple (how to edit existing products):

$product = \Drupal\commerce_product\Entity\Product::load($product_id);

With this code you can load the product, next you can apply your changes, and next just:

$product->save(); 
sun-fire’s picture

About #16 - I think you can try something like this:

  $result = \Drupal::entityQuery('commerce_product')
      ->condition('product_id', $product_id)
      ->execute();

  entity_delete_multiple('commerce_product', $result);

I didn't tested that code, but I think, it's should working, because entity_delete_multiple is a standard function for working with entities.

bharat.kelotra’s picture

How can we load a product/variation by sku (not product id) and do update of price/status programmatically?

mglaman’s picture

There's a lot of support comments here, which are off topic.

Your going to get better results by posting questions at https://drupal.stackexchange.com/. Most of these are generic Drupal questions and not specific to just Commerce.

mglaman’s picture

Priority: Critical » Normal
Parthvi’s picture

Hi,

Thanks for the reply.

I can add translation for category via below code.
$term1NL = $term1->addTranslation('nl');
$term1NL->name = 'nl version';
$term1NL->vid = 'main cat';
$term1NL->save();

How can I edit translation?

.jch’s picture

Updating all variations => When editing a product and saving, it's variations are not updated. If fields in the variations are comprised of fields from the parent product type such as a SKU partially made from tokens derived from drop-down lists in product type, then these fields are not updated in the product variations unless manually going into each variation and doing a Edit / Update on every variation to synchronize fields.

What would the code look like in order to include an option to Update ALL variations on the product edit form, Or simply adding
a variation update loop to Product Edit Save?

What's needed is possibly some kind of bulk update to synchronize products variations with their product type and probably orders as well.

wanjee’s picture

Recipe to programmatically create a product (and its variation) fails in my case:

      $variation = ProductVariation::create([
        'type' => 'default',
        'sku' => 'my-sku',
        'status' => 1,
        'price' => new Price('42.00', 'EUR'),
      ]);
      $variation->save();

      $product = Product::create([
        'uid' => 1,
        'type' => 'my_product_type',
        'title' => 'my title',
        'variations' => [
          $variation,
        ],
      ]);

      $product->save();

I get following error:

Error: Call to a member function isEmpty() on null in Drupal\commerce_product\Entity\Product->postSave() (line 285 of [...]/modules/contrib/commerce/modules/product/src/Entity/Product.php) #0 [...]/core/lib/Drupal/Core/Entity/EntityStorageBase.php(469): Drupal\commerce_product\Entity\Product->postSave(Object(Drupal\commerce\CommerceContentEntityStorage), false)

Are #3 documentation still up-to-date ? I use 8.x-2.3.

Thanks

drugan’s picture

As an alternative you may try for this purpose the Commerce Bulk module:

https://www.drupal.org/project/commerce_bulk

See how the Commerce Generate submodule generates products using Variations Creator service:

https://github.com/drugan/commerce_bulk/blob/8.x-1.x/modules/commerce_ge...

Also, see how variations are bulk created for a product:

https://github.com/drugan/commerce_bulk/blob/8.x-1.x/commerce_bulk.modul...

Explore the service class methods:

https://github.com/drugan/commerce_bulk/blob/8.x-1.x/src/BulkVariationsC...

wanjee’s picture

Hi drugan, thanks for the insights.

I was able to make some progress looking at your code. Main change is the removal of $variation->save(); as it does not seem necessary.

      $variation = \Drupal::entityTypeManager()->getStorage('commerce_product_variation')->create([
        'type' => 'default',
        'sku' => 'my-sku',
        'status' => 1,
        'price' => new Price('42.00', 'EUR'),
      ]);

      $product = \Drupal::entityTypeManager()->getStorage('commerce_product')->create([
        'uid' => 1,
        'type' => 'my_product_type',
        'title' => 'my title',
      ]);

      $product->setVariations([$variation]);
      $product->save();

This is the working code.

Thanks !

swatiphp’s picture

@mbreden Thanks for code example.
I would like to know how can I set value for custom fields created on "default" type from manage fields.

taggartj’s picture

Bookmarked ! :)

taggartj’s picture

Also can look in to using the Services ... in my case was simply trying to programmatically add stuff to a the cart:

// this is in a 
public static function create(ContainerInterface $container) {....

  $container->get('entity_type.manager'), $this->entityTypeManager
  $container->get('current_user'), ($this->currentUser)
  // SEE SERVICES ... 
  $container->get('commerce_cart.cart_provider'),  ( $this->commerceCartProvider ) 
  $container->get('commerce_cart.cart_manager') ($this->commerceCartManager)

/// then in some function
    $variation_id = 3; // <- the id of the product variation.
    $commerce_product_variation_storage = $this->entityTypeManager->getStorage('commerce_product_variation');
    $variation = $commerce_product_variation_storage->load($variation_id);
    $all_carts = $this->commerceCartProvider->getCarts();
    if (!empty($all_carts)) {
      if (count($all_carts) == 1) {
        $cart = reset($all_carts);
        $this->commerceCartManager->addEntity($cart, $variation);
        $responce = ['message' => 'Item added to cart.'];
        $status = 200;
      }
      else {
        // @TODO shit we have an issue here. multiple carts.
        $responce = ['message' => 'multiple carts not supported yet'];
        $status = 400;
      }
    }
    else {
      // Create one here.
      $user = $this->currentUser;
      $account = $user->getAccount();
      if (!empty($account)) {
        $product = $variation->product_id->referencedEntities()[0];
        $stores = $product->stores->referencedEntities();
        if (!empty($stores)) {
          if (count($stores) == 1) {
            $store = reset($stores);
          }
          else {
            //@TODO refactor for multiple stores???
            // Grab the first one for now.
            $store = $stores[0];
          }
          $cart = $this->commerceCartProvider->createCart('default', $store, $account);
          $this->commerceCartManager->addEntity($cart, $variation);
          $responce = ['message' => 'Item added to new cart.'];
        }
handkerchief’s picture

Attention: You can't remove the variation->save() method as it says in #26 if you are in a product presave hook. For that you have to save the variation first, otherwise you'll get this error:

Drupal\Core\Entity\EntityStorageException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'variations_target_id' cannot be null

liquidcms’s picture

Was trying to write a function to create a cart with line items from a product id. Using excellent write up from #3 above (with a couple minor tweaks) i was able to get the Order created; but no cart.

Doesn't look like the Commerce docs referenced above have anything on Cart programming; but on a whim.. i tried setting $order->cart = true.. and sure enough.. i got a cart created. :)

liquidcms’s picture

Hmm.. no, not quite.. still issues with this.

If i add again, then /cart has multiple carts with multiple checkout buttons. Even after i delete the orders. Seems like still a few bugs in this.

ZRoman’s picture

i create product this code

use Drupal\commerce_product\Entity\ProductVariation;
use Drupal\commerce_product\Entity\Product;
use Drupal\commerce_price\Price;

$variation1 = ProductVariation::create([
    'type' => 'default',
    'sku' => 'var1',
    'price' => new Price('24.00', 'EUR'),
]);
$variation1->save();

// Create product using variations previously saved

$product = Product::create([
    'type' => 'default',
    'title' => t('Your Product Name'),
    'variations' => [$variation1],
]);
$product->save();

, but look error

Fatal error: Class 'Drupal\Tests\commerce\Kernel\CommerceKernelTestBase' not found...