Get parent of Product by ID - shopware

I'm trying to get a Product with his parent by product ID and I have this code:
/**
* #param string[] $ids
* #return ProductCollection|EntityCollection
*/
public function getProductsByIds(array $ids): ProductCollection
{
$criteria = new Criteria($ids);
$criteria->addAssociation('parent');
return $this->productRepository
->search($criteria, Context::createDefaultContext())
->getEntities();
}
But the problem is when I'm running this code:
$products = $this->productService->getProductsByIds([$id]);
$product = $products->first();
dd($product->getParent());
I'm getting null every time.
How can I get a Product parent and where can I read more about the 'paths' for the Association method?

When you execute your method you get EntityCollection, and not Single entity as result. So you should use the following code:
$products = $this->productService->getProductByIds([$id]);
foreach($products as $product) {
dd($product->getParent());
}
And make sure that your products have parents of course:)

On the 6.3.4.0 version of Shopware, I'm getting the error 400 now.
It is not possible to read the parent association directly. Please read the parents via a separate call over the repository
So I have removed Association and created a separate method to fetch one Product with a parent.
public function getProductById(?string $id): ?ProductEntity
{
if ($id === null) {
return null;
}
$products = $this->getProductsByIds([$id]);
$product = $products->first();
$parentProductId = $product?->getParentId() ?? null;
$parentProduct = $this->getProductById($parentProductId);
if ($parentProduct !== null) {
$product->setParent($parentProduct);
}
return $product;
}

Related

Access properties of Entities

I'm getting all active countries via the service id country.repository
public function getCountries(Context $context): EntityCollection
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('active', true));
return $this->countryRepository->search($criteria, $context)->getEntities();
}
This gives me this CountryCollection:
How can I access each element to get the id and the name?
I tried to loop over
public function test($context): array
{
$countryIds = $this->getCountries($context);
$ids = [];
foreach ($countryIds as $countryId) {
$ids[] = $countryId['id'];
}
return $ids;
}
Obviously this doesn't work. It gives this error:
Cannot use object of type Shopware\Core\System\Country\CountryEntity
as array
If you are only interested in the ids of the countries you can use
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('active', true));
$ids = $this->countryRepository->searchIds($criteria, $context)->getIds();
searchIds() will only return the ids of the entities and not all of their properties, the benefit is that not all values have to be loaded from the DB and hydrated, therefore searchIds() is faster then the normal search().
You need to call the function like this
public function test($context): array
{
$countries = $this->getCountries($context);
$ids = [];
foreach ($countries as $country) {
$ids[] = $country->getId();//or $country->getName()
}
return $ids;
}
Usually you need to open the entity file. In your case it is CountryEntity.php to check the function to get the id or other fields there.

Media creation via php in Shopware 6

i'm struggling get a media import via PHP for Shopware 6 to work.
This is my service:
<?php declare(strict_types=1);
namespace My\Namespace\Service;
use Shopware\Core\Content\Media\File\MediaFile;
use Shopware\Core\Content\Media\MediaService;
use Shopware\Core\Framework\Context;
class ImageImport
{
/**
* #var MediaService
*/
protected $mediaService;
/**
* ImageImport constructor.
* #param MediaService $mediaService
*/
public function __construct(MediaService $mediaService)
{
$this->mediaService = $mediaService;
}
public function addImageToProductMedia($imageUrl, Context $context)
{
$mediaId = NULL;
$context->disableCache(function (Context $context) use ($imageUrl, &$mediaId): void {
$filePathParts = explode('/', $imageUrl);
$fileName = array_pop($filePathParts);
$fileNameParts = explode('.', $fileName);
$actualFileName = $fileNameParts[0];
$fileExtension = $fileNameParts[1];
if ($actualFileName && $fileExtension) {
$tempFile = tempnam(sys_get_temp_dir(), 'image-import');
file_put_contents($tempFile, file_get_contents($imageUrl));
$fileSize = filesize($tempFile);
$mimeType = mime_content_type($tempFile);
$mediaFile = new MediaFile($tempFile, $mimeType, $fileExtension, $fileSize);
$mediaId = $this->mediaService->saveMediaFile($mediaFile, $actualFileName, $context, 'product');
}
});
return $mediaId;
}
}
A entry in the table media with the correct media_folder_association is created. And as far as i can see there are no differences to other medias uploaded via backend (except private is 1 and user_id is NULL).
But in the backend the media entries are broken, seems like it can not load the actual image file (i've tried to set private to true to see it in the media section, same happens when adding the media to a product via php, but i guess the problem is before any assignment to products).
Image in backend media
Has anybody a suggestion whats wrong here?
Thanks
Phil
===== SOLUTION ======
Here is the updated and working service:
<?php declare(strict_types=1);
namespace My\Namespace\Service;
use Shopware\Core\Content\Media\File\FileSaver;
use Shopware\Core\Content\Media\File\MediaFile;
use Shopware\Core\Content\Media\MediaService;
use Shopware\Core\Framework\Context;
class ImageImport
{
/**
* #var MediaService
*/
protected $mediaService;
/**
* #var FileSaver
*/
private $fileSaver;
/**
* ImageImport constructor.
* #param MediaService $mediaService
* #param FileSaver $fileSaver
*/
public function __construct(MediaService $mediaService, FileSaver $fileSaver)
{
$this->mediaService = $mediaService;
$this->fileSaver = $fileSaver;
}
public function addImageToProductMedia($imageUrl, Context $context)
{
$mediaId = NULL;
$context->disableCache(function (Context $context) use ($imageUrl, &$mediaId): void {
$filePathParts = explode('/', $imageUrl);
$fileName = array_pop($filePathParts);
$fileNameParts = explode('.', $fileName);
$actualFileName = $fileNameParts[0];
$fileExtension = $fileNameParts[1];
if ($actualFileName && $fileExtension) {
$tempFile = tempnam(sys_get_temp_dir(), 'image-import');
file_put_contents($tempFile, file_get_contents($imageUrl));
$fileSize = filesize($tempFile);
$mimeType = mime_content_type($tempFile);
$mediaFile = new MediaFile($tempFile, $mimeType, $fileExtension, $fileSize);
$mediaId = $this->mediaService->createMediaInFolder('product', $context, false);
$this->fileSaver->persistFileToMedia(
$mediaFile,
$actualFileName,
$mediaId,
$context
);
}
});
return $mediaId;
}
}
In order to import files to Shopware 6 theres two steps which are necessary:
You have to create a media file object (MediaDefinition / media table). Take a look at the MediaConverter
Create a new entry in the SwagMigrationMediaFileDefinition (swag_migration_media_file table).
Each entry in the swag_migration_media_file table of the associated migration run will get processed by an implementation of MediaFileProcessorInterface.
To add a file to the table you can do something like this in your Converter class (this example is from the MediaConverter):
abstract class MediaConverter extends ShopwareConverter
{
public function convert(
array $data,
Context $context,
MigrationContextInterface $migrationContext
): ConvertStruct {
$this->generateChecksum($data);
$this->context = $context;
$this->locale = $data['_locale'];
unset($data['_locale']);
$connection = $migrationContext->getConnection();
$this->connectionId = '';
if ($connection !== null) {
$this->connectionId = $connection->getId();
}
$converted = [];
$this->mainMapping = $this->mappingService->getOrCreateMapping(
$this->connectionId,
DefaultEntities::MEDIA,
$data['id'],
$context,
$this->checksum
);
$converted['id'] = $this->mainMapping['entityUuid'];
if (!isset($data['name'])) {
$data['name'] = $converted['id'];
}
$this->mediaFileService->saveMediaFile(
[
'runId' => $migrationContext->getRunUuid(),
'entity' => MediaDataSet::getEntity(), // important to distinguish between private and public files
'uri' => $data['uri'] ?? $data['path'],
'fileName' => $data['name'], // uri or path to the file (because of the different implementations of the gateways)
'fileSize' => (int) $data['file_size'],
'mediaId' => $converted['id'], // uuid of the media object in Shopware 6
]
);
unset($data['uri'], $data['file_size']);
$this->getMediaTranslation($converted, $data);
$this->convertValue($converted, 'title', $data, 'name');
$this->convertValue($converted, 'alt', $data, 'description');
$albumMapping = $this->mappingService->getMapping(
$this->connectionId,
DefaultEntities::MEDIA_FOLDER,
$data['albumID'],
$this->context
);
if ($albumMapping !== null) {
$converted['mediaFolderId'] = $albumMapping['entityUuid'];
$this->mappingIds[] = $albumMapping['id'];
}
unset(
$data['id'],
$data['albumID'],
// Legacy data which don't need a mapping or there is no equivalent field
$data['path'],
$data['type'],
$data['extension'],
$data['file_size'],
$data['width'],
$data['height'],
$data['userID'],
$data['created']
);
$returnData = $data;
if (empty($returnData)) {
$returnData = null;
}
$this->updateMainMapping($migrationContext, $context);
// The MediaWriter will write this Shopware 6 media object
return new ConvertStruct($converted, $returnData, $this->mainMapping['id']);
}
}
swag_migration_media_files are processed by the right processor service. This service is different for documents and normal media, but it still is gateway dependent
=== DIFFERENT APPROACH (Shyim suggestion) ===
Take a look at this (taken from Shopwaredowntown's Github repository):
public function upload(UploadedFile $file, string $folder, string $type, Context $context): string
{
$this->checkValidFile($file);
$this->validator->validate($file, $type);
$mediaFile = new MediaFile($file->getPathname(), $file->getMimeType(), $file->getClientOriginalExtension(), $file->getSize());
$mediaId = $this->mediaService->createMediaInFolder($folder, $context, false);
try {
$this->fileSaver->persistFileToMedia(
$mediaFile,
pathinfo($file->getFilename(), PATHINFO_FILENAME),
$mediaId,
$context
);
} catch (MediaNotFoundException $e) {
throw new UploadException($e->getMessage());
}
return $mediaId;
}
src/Portal/Hacks/StorefrontMediaUploader.php:49
public function upload(UploadedFile $file, string $folder, string $type, Context $context): string

How to add existing attribute to an attribute set programmatically in Magento 2

How can I add some existing attributes to a new attribute set programmatically in Magento 2?
in magento 2, there is color & manufactor attribute already created, but by defaultly this two attribute is not assigned to default attribute set. so, we can do like this. then it will assign this two attribute to default attribute when module installing.
<?php
namespace Vendor\Module\Setup;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
class InstallData implements InstallDataInterface
{
private $eavSetupFactory;
private $categorySetupFactory;
public function __construct(EavSetupFactory $eavSetupFactory, \Magento\Catalog\Setup\CategorySetupFactory $categorySetupFactory)
{
$this->eavSetupFactory = $eavSetupFactory;
$this->categorySetupFactory = $categorySetupFactory;
}
public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
{
try {
/**
* #var \Magento\Eav\Setup\EavSetup
*/
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
/**
* #var \Magento\Catalog\Setup\CategorySetup
*/
$categorySetup = $this->categorySetupFactory->create(['setup' => $setup]);
$entityTypeId = $categorySetup->getEntityTypeId(\Magento\Catalog\Model\Product::ENTITY);
$attributeSetId = $categorySetup->getDefaultAttributeSetId($entityTypeId); // get default attribute set id
$attrGroupId = $categorySetup->getDefaultAttributeGroupId($entityTypeId, $attributeSetId); // get default attribute group id (in my case, it returns id of 'Product Details' group)
$colorAttr = $eavSetup->getAttribute(\Magento\Catalog\Model\Product::ENTITY, 'color');
if($colorAttr) {
$eavSetup->addAttributeToGroup(
$entityTypeId,
$attributeSetId,
$attrGroupId,
'color',
null
);
}
$manufacturerAttr = $eavSetup->getAttribute(\Magento\Catalog\Model\Product::ENTITY, 'manufacturer');
if($manufacturerAttr) {
$eavSetup->addAttributeToGroup(
$entityTypeId,
$attributeSetId,
$attrGroupId,
'manufacturer',
null
);
}
} catch (NoSuchEntityException $e) {
return;
} catch (\Exception $e) {
return;
}
}

I wanted to update an existing record in asp.net mvc using Entity framework

I wanted to check if the productDiscount entity is already exist in the database, if it is exist then I wanted to update it. But instead of updating the entity the following code adds a new one. How to solve this problem..
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ProductDiscount(ProductDiscount productDiscount)
{
if (!ModelState.IsValid)
{
var viewModel = new ViewModelProductDiscount()
{
Products = _context.Products.ToList()
};
return View(viewModel);
}
var id = productDiscount.ProductId;
var disInDb = _context.ProductDiscounts.FirstOrDefault(p => p.ProductId == id);
if (disInDb==null)
{
_context.ProductDiscounts.Add(productDiscount);
_context.SaveChanges();
}
else
{
_context.ProductDiscounts.Add(disInDb);
_context.SaveChanges();
return Content(disInDb.Id.ToString());
}
return RedirectToAction("Products");
}
from the else block remove _context.ProductDiscounts.Add(disInDb); Just map the productDiscount values into disInDb and then _context.SaveChanges();
Like
else
{
disInDb.Name = productDiscount.Name;
disInDb.Discription = productDiscount.Discription;
_context.SaveChanges();
}
I don't really know how your ProductDiscount object looks like so I will assume your object props but it should give you an idea of how to update the record. Inside your else this line _context.ProductDiscounts.Add(disInDb); is what is causing the issue. Instead you want to do the following inside the else block
else
{
disInDb.DiscountCode = "somecode"; //change this to the correct prop name which needs updating
disInDb.DiscountProductName = "somename"; //change this to the correct prop name which needs updating
_context.SaveChanges();
}

Anonymous type and getting values out side of method scope

I am building an asp.net site in .net framework 4.0, and I am stuck at the method that supposed to call a .cs class and get the query result back here is my method call and method
1: method call form aspx.cs page:
helper cls = new helper();
var query = cls.GetQuery(GroupID,emailCap);
2: Method in helper class:
public IQueryable<VariablesForIQueryble> GetQuery(int incomingGroupID, int incomingEmailCap)
{
var ctx = new some connection_Connection();
ObjectSet<Members1> members = ctx.Members11;
ObjectSet<groupMember> groupMembers = ctx.groupMembers;
var query = from m in members
join gm in groupMembers on m.MemberID equals gm.MemID
where (gm.groupID == incomingGroupID) && (m.EmailCap == incomingEmailCap)
select new VariablesForIQueryble(m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap);
//select new {m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap};
return query ;
}
I tried the above code with IEnumerable too without any luck. This is the code for class VariablesForIQueryble:
3:Class it self for taking anonymouse type and cast it to proper types:
public class VariablesForIQueryble
{
private int _emailCap;
public int EmailCap
{
get { return _emailCap; }
set { _emailCap = value; }
}`....................................
4: and a constructor:
public VariablesForIQueryble(int memberID, string memberFirst, string memberLast, string memberEmail, int? validEmail, int? emailCap)
{
this.EmailCap = (int) emailCap;
.........................
}
I can't seem to get the query result back, first it told me anonymous type problem, I made a class after reading this: link text; and now it tells me constructors with parameters not supported. Now I am an intermediate developer, is there an easy solution to this or do I have to take my query back to the .aspx.cs page.
If you want to project to a specific type .NET type like this you will need to force the query to actually happen using either .AsEnumerable() or .ToList() and then use .Select() against linq to objects.
You could leave your original anonymous type in to specify what you want back from the database, then call .ToList() on it and then .Select(...) to reproject.
You can also clean up your code somewhat by using an Entity Association between Groups and Members using a FK association in the database. Then the query becomes a much simpler:
var result = ctx.Members11.Include("Group").Where(m => m.Group.groupID == incomingGroupID && m.EmailCap == incomingEmailCap);
You still have the issue of having to do a select to specify which columns to return and then calling .ToList() to force execution before reprojecting to your new type.
Another alternative is to create a view in your database and import that as an Entity into the Entity Designer.
Used reflection to solve the problem:
A: Query, not using custom made "VariablesForIQueryble" class any more:
//Method in helper class
public IEnumerable GetQuery(int incomingGroupID, int incomingEmailCap)
{
var ctx = new some_Connection();
ObjectSet<Members1> members = ctx.Members11;
ObjectSet<groupMember> groupMembers = ctx.groupMembers;
var query = from m in members
join gm in groupMembers on m.MemberID equals gm.MemID
where ((gm.groupID == incomingGroupID) && (m.EmailCap == incomingEmailCap)) //select m;
select new { m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap };
//select new VariablesForIQueryble (m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap);
//List<object> lst = new List<object>();
//foreach (var i in query)
//{
// lst.Add(i.MemberEmail);
//}
//return lst;
//return query.Select(x => new{x.MemberEmail,x.MemberID,x.ValidEmail,x.MemberFirst,x.MemberLast}).ToList();
return query;
}
B:Code to catch objects and conversion of those objects using reflection
helper cls = new helper();
var query = cls.GetQuery(GroupID,emailCap);
if (query != null)
{
foreach (var objRow in query)
{
System.Type type = objRow.GetType();
int memberId = (int)type.GetProperty("MemberID").GetValue(objRow, null);
string memberEmail = (string)type.GetProperty("MemberEmail").GetValue(objRow, null);
}
else
{
something else....
}

Resources