616 lines
22 KiB
PHP
616 lines
22 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Exception;
|
|
|
|
class MagentoCategoryMigrationService
|
|
{
|
|
protected $magento1Connection;
|
|
protected $magento2Connection;
|
|
protected $magento1Prefix;
|
|
protected $magento2Prefix;
|
|
protected $storeMapping = [];
|
|
protected $categoryMapping = [];
|
|
protected $migrationLog = [];
|
|
|
|
public function __construct()
|
|
{
|
|
$this->magento1Connection = 'magento1';
|
|
$this->magento2Connection = 'magento2';
|
|
$this->magento1Prefix = config('database.connections.magento1.prefix', '');
|
|
$this->magento2Prefix = config('database.connections.magento2.prefix', '');
|
|
}
|
|
|
|
/**
|
|
* Get all stores from Magento 1
|
|
*/
|
|
public function getMagento1Stores()
|
|
{
|
|
try {
|
|
$stores = DB::connection($this->magento1Connection)
|
|
->table($this->magento1Prefix . 'core_store')
|
|
->select('store_id', 'code', 'name', 'website_id', 'group_id')
|
|
->where('store_id', '>', 0)
|
|
->get();
|
|
|
|
return $stores;
|
|
} catch (Exception $e) {
|
|
Log::error('Error fetching Magento 1 stores: ' . $e->getMessage());
|
|
return collect([]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all stores from Magento 2
|
|
*/
|
|
public function getMagento2Stores()
|
|
{
|
|
try {
|
|
$stores = DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'store')
|
|
->select('store_id', 'code', 'name', 'website_id')
|
|
->where('store_id', '>', 0)
|
|
->get();
|
|
|
|
return $stores;
|
|
} catch (Exception $e) {
|
|
Log::error('Error fetching Magento 2 stores: ' . $e->getMessage());
|
|
return collect([]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all categories from Magento 1 for a specific store
|
|
*/
|
|
public function getMagento1Categories($storeId = null)
|
|
{
|
|
try {
|
|
// Get entity type ID
|
|
$entityTypeId = DB::connection($this->magento1Connection)
|
|
->table($this->magento1Prefix . 'eav_entity_type')
|
|
->where('entity_type_code', 'catalog_category')
|
|
->value('entity_type_id');
|
|
|
|
if (!$entityTypeId) {
|
|
return collect([]);
|
|
}
|
|
|
|
// Get attribute IDs
|
|
$nameAttributeId = DB::connection($this->magento1Connection)
|
|
->table($this->magento1Prefix . 'eav_attribute')
|
|
->where('entity_type_id', $entityTypeId)
|
|
->where('attribute_code', 'name')
|
|
->value('attribute_id');
|
|
|
|
$isActiveAttributeId = DB::connection($this->magento1Connection)
|
|
->table($this->magento1Prefix . 'eav_attribute')
|
|
->where('entity_type_id', $entityTypeId)
|
|
->where('attribute_code', 'is_active')
|
|
->value('attribute_id');
|
|
|
|
$urlKeyAttributeId = DB::connection($this->magento1Connection)
|
|
->table($this->magento1Prefix . 'eav_attribute')
|
|
->where('entity_type_id', $entityTypeId)
|
|
->where('attribute_code', 'url_key')
|
|
->value('attribute_id');
|
|
|
|
$targetStoreId = $storeId ?? 0;
|
|
|
|
// Get base category data
|
|
$categories = DB::connection($this->magento1Connection)
|
|
->table($this->magento1Prefix . 'catalog_category_entity')
|
|
->select('entity_id', 'parent_id', 'level', 'path', 'position')
|
|
->orderBy('level')
|
|
->orderBy('position')
|
|
->get();
|
|
|
|
// Get attribute values
|
|
$nameValues = [];
|
|
$isActiveValues = [];
|
|
$urlKeyValues = [];
|
|
|
|
if ($nameAttributeId) {
|
|
$nameValues = DB::connection($this->magento1Connection)
|
|
->table($this->magento1Prefix . 'catalog_category_entity_varchar')
|
|
->where('attribute_id', $nameAttributeId)
|
|
->where('store_id', $targetStoreId)
|
|
->pluck('value', 'entity_id')
|
|
->toArray();
|
|
}
|
|
|
|
if ($isActiveAttributeId) {
|
|
$isActiveValues = DB::connection($this->magento1Connection)
|
|
->table($this->magento1Prefix . 'catalog_category_entity_int')
|
|
->where('attribute_id', $isActiveAttributeId)
|
|
->where('store_id', $targetStoreId)
|
|
->pluck('value', 'entity_id')
|
|
->toArray();
|
|
}
|
|
|
|
if ($urlKeyAttributeId) {
|
|
$urlKeyValues = DB::connection($this->magento1Connection)
|
|
->table($this->magento1Prefix . 'catalog_category_entity_varchar')
|
|
->where('attribute_id', $urlKeyAttributeId)
|
|
->where('store_id', $targetStoreId)
|
|
->pluck('value', 'entity_id')
|
|
->toArray();
|
|
}
|
|
|
|
// Combine data
|
|
return $categories->map(function($category) use ($nameValues, $isActiveValues, $urlKeyValues) {
|
|
$category->name = $nameValues[$category->entity_id] ?? null;
|
|
$category->is_active = $isActiveValues[$category->entity_id] ?? null;
|
|
$category->url_key = $urlKeyValues[$category->entity_id] ?? null;
|
|
return $category;
|
|
});
|
|
} catch (Exception $e) {
|
|
Log::error('Error fetching Magento 1 categories: ' . $e->getMessage());
|
|
return collect([]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all categories from Magento 2 for a specific store
|
|
*/
|
|
public function getMagento2Categories($storeId = null)
|
|
{
|
|
try {
|
|
// Get entity type ID
|
|
$entityTypeId = DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'eav_entity_type')
|
|
->where('entity_type_code', 'catalog_category')
|
|
->value('entity_type_id');
|
|
|
|
if (!$entityTypeId) {
|
|
return collect([]);
|
|
}
|
|
|
|
// Get attribute IDs
|
|
$nameAttributeId = DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'eav_attribute')
|
|
->where('entity_type_id', $entityTypeId)
|
|
->where('attribute_code', 'name')
|
|
->value('attribute_id');
|
|
|
|
$isActiveAttributeId = DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'eav_attribute')
|
|
->where('entity_type_id', $entityTypeId)
|
|
->where('attribute_code', 'is_active')
|
|
->value('attribute_id');
|
|
|
|
$urlKeyAttributeId = DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'eav_attribute')
|
|
->where('entity_type_id', $entityTypeId)
|
|
->where('attribute_code', 'url_key')
|
|
->value('attribute_id');
|
|
|
|
$targetStoreId = $storeId ?? 0;
|
|
|
|
// Get base category data
|
|
$categories = DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'catalog_category_entity')
|
|
->select('entity_id', 'parent_id', 'level', 'path', 'position')
|
|
->orderBy('level')
|
|
->orderBy('position')
|
|
->get();
|
|
|
|
// Get attribute values
|
|
$nameValues = [];
|
|
$isActiveValues = [];
|
|
$urlKeyValues = [];
|
|
|
|
if ($nameAttributeId) {
|
|
$nameValues = DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'catalog_category_entity_varchar')
|
|
->where('attribute_id', $nameAttributeId)
|
|
->where('store_id', $targetStoreId)
|
|
->pluck('value', 'entity_id')
|
|
->toArray();
|
|
}
|
|
|
|
if ($isActiveAttributeId) {
|
|
$isActiveValues = DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'catalog_category_entity_int')
|
|
->where('attribute_id', $isActiveAttributeId)
|
|
->where('store_id', $targetStoreId)
|
|
->pluck('value', 'entity_id')
|
|
->toArray();
|
|
}
|
|
|
|
if ($urlKeyAttributeId) {
|
|
$urlKeyValues = DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'catalog_category_entity_varchar')
|
|
->where('attribute_id', $urlKeyAttributeId)
|
|
->where('store_id', $targetStoreId)
|
|
->pluck('value', 'entity_id')
|
|
->toArray();
|
|
}
|
|
|
|
// Combine data
|
|
return $categories->map(function($category) use ($nameValues, $isActiveValues, $urlKeyValues) {
|
|
$category->name = $nameValues[$category->entity_id] ?? null;
|
|
$category->is_active = $isActiveValues[$category->entity_id] ?? null;
|
|
$category->url_key = $urlKeyValues[$category->entity_id] ?? null;
|
|
return $category;
|
|
});
|
|
} catch (Exception $e) {
|
|
Log::error('Error fetching Magento 2 categories: ' . $e->getMessage());
|
|
return collect([]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build hierarchical category tree structure
|
|
*/
|
|
public function buildCategoryTreeHierarchy($categories)
|
|
{
|
|
$categoryMap = [];
|
|
$rootCategories = [];
|
|
|
|
// First pass: create a map of all categories with children array
|
|
foreach ($categories as $category) {
|
|
$categoryMap[$category->entity_id] = [
|
|
'id' => $category->entity_id,
|
|
'name' => $category->name ?? 'Unnamed Category',
|
|
'parent_id' => $category->parent_id,
|
|
'level' => $category->level ?? 0,
|
|
'position' => $category->position ?? 0,
|
|
'is_active' => $category->is_active ?? 0,
|
|
'path' => $category->path ?? '',
|
|
'children' => []
|
|
];
|
|
}
|
|
|
|
// Second pass: build the tree structure by assigning children to parents
|
|
foreach ($categoryMap as $id => $category) {
|
|
$parentId = $category['parent_id'];
|
|
|
|
// Skip if parent is self (prevent circular references)
|
|
if ($parentId == $id) {
|
|
$rootCategories[] = &$categoryMap[$id];
|
|
continue;
|
|
}
|
|
|
|
// Check if this is a root category (parent is 0, 1, or doesn't exist in map)
|
|
if ($parentId == 0 || $parentId == 1 || !isset($categoryMap[$parentId])) {
|
|
$rootCategories[] = &$categoryMap[$id];
|
|
} else {
|
|
// Add as child of parent
|
|
$categoryMap[$parentId]['children'][] = &$categoryMap[$id];
|
|
}
|
|
}
|
|
|
|
// Sort function for categories
|
|
$sortCategories = function(&$categories) use (&$sortCategories) {
|
|
usort($categories, function($a, $b) {
|
|
return $a['position'] <=> $b['position'];
|
|
});
|
|
// Recursively sort children
|
|
foreach ($categories as &$category) {
|
|
if (!empty($category['children'])) {
|
|
$sortCategories($category['children']);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Sort root categories and all children recursively
|
|
$sortCategories($rootCategories);
|
|
|
|
return $rootCategories;
|
|
}
|
|
|
|
/**
|
|
* Migrate categories from Magento 1 to Magento 2
|
|
*/
|
|
public function migrateCategories($storeMapping = [])
|
|
{
|
|
$this->storeMapping = $storeMapping;
|
|
$this->migrationLog = [];
|
|
$this->categoryMapping = [];
|
|
|
|
try {
|
|
DB::connection($this->magento2Connection)->beginTransaction();
|
|
|
|
// Get root category ID for Magento 2 (usually 2)
|
|
$rootCategoryId = $this->getMagento2RootCategoryId();
|
|
|
|
// Get all categories from Magento 1
|
|
$m1Categories = $this->getMagento1Categories();
|
|
|
|
// Build category tree
|
|
$categoryTree = $this->buildCategoryTree($m1Categories);
|
|
|
|
// Migrate categories level by level
|
|
foreach ($categoryTree as $level => $categories) {
|
|
foreach ($categories as $m1Category) {
|
|
$this->migrateCategory($m1Category, $rootCategoryId);
|
|
}
|
|
}
|
|
|
|
// Migrate category attributes for each store
|
|
foreach ($this->storeMapping as $m1StoreId => $m2StoreId) {
|
|
$this->migrateCategoryAttributes($m1StoreId, $m2StoreId);
|
|
}
|
|
|
|
DB::connection($this->magento2Connection)->commit();
|
|
|
|
return [
|
|
'success' => true,
|
|
'message' => 'Categories migrated successfully',
|
|
'migrated_count' => count($this->categoryMapping),
|
|
'log' => $this->migrationLog
|
|
];
|
|
|
|
} catch (Exception $e) {
|
|
DB::connection($this->magento2Connection)->rollBack();
|
|
Log::error('Category migration error: ' . $e->getMessage());
|
|
|
|
return [
|
|
'success' => false,
|
|
'message' => 'Migration failed: ' . $e->getMessage(),
|
|
'log' => $this->migrationLog
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build category tree organized by level
|
|
*/
|
|
protected function buildCategoryTree($categories)
|
|
{
|
|
$tree = [];
|
|
foreach ($categories as $category) {
|
|
$level = $category->level ?? 1;
|
|
if (!isset($tree[$level])) {
|
|
$tree[$level] = [];
|
|
}
|
|
$tree[$level][] = $category;
|
|
}
|
|
ksort($tree);
|
|
return $tree;
|
|
}
|
|
|
|
/**
|
|
* Migrate a single category
|
|
*/
|
|
protected function migrateCategory($m1Category, $parentId = null)
|
|
{
|
|
try {
|
|
// Check if category already exists
|
|
if (isset($this->categoryMapping[$m1Category->entity_id])) {
|
|
return $this->categoryMapping[$m1Category->entity_id];
|
|
}
|
|
|
|
// Determine parent ID
|
|
if ($m1Category->parent_id == 1 || $m1Category->parent_id == 0) {
|
|
$m2ParentId = $parentId ?? $this->getMagento2RootCategoryId();
|
|
} else {
|
|
$m2ParentId = $this->categoryMapping[$m1Category->parent_id] ?? $parentId;
|
|
}
|
|
|
|
// Insert category entity
|
|
$m2EntityId = DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'catalog_category_entity')
|
|
->insertGetId([
|
|
'attribute_set_id' => 3, // Default category attribute set
|
|
'parent_id' => $m2ParentId,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
'path' => '',
|
|
'position' => $m1Category->position ?? 0,
|
|
'level' => $m1Category->level ?? 1,
|
|
'children_count' => 0,
|
|
]);
|
|
|
|
// Update path
|
|
$path = $this->buildCategoryPath($m2EntityId, $m2ParentId);
|
|
DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'catalog_category_entity')
|
|
->where('entity_id', $m2EntityId)
|
|
->update(['path' => $path]);
|
|
|
|
// Store mapping
|
|
$this->categoryMapping[$m1Category->entity_id] = $m2EntityId;
|
|
|
|
$this->migrationLog[] = "Migrated category ID {$m1Category->entity_id} -> {$m2EntityId} ({$m1Category->name})";
|
|
|
|
return $m2EntityId;
|
|
|
|
} catch (Exception $e) {
|
|
Log::error("Error migrating category {$m1Category->entity_id}: " . $e->getMessage());
|
|
$this->migrationLog[] = "ERROR: Failed to migrate category ID {$m1Category->entity_id}: " . $e->getMessage();
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build category path
|
|
*/
|
|
protected function buildCategoryPath($entityId, $parentId)
|
|
{
|
|
if ($parentId == $this->getMagento2RootCategoryId()) {
|
|
return "1/{$parentId}/{$entityId}";
|
|
}
|
|
|
|
$parent = DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'catalog_category_entity')
|
|
->where('entity_id', $parentId)
|
|
->first();
|
|
|
|
if ($parent && $parent->path) {
|
|
return $parent->path . '/' . $entityId;
|
|
}
|
|
|
|
return "1/{$parentId}/{$entityId}";
|
|
}
|
|
|
|
/**
|
|
* Migrate category attributes for a specific store
|
|
*/
|
|
protected function migrateCategoryAttributes($m1StoreId, $m2StoreId)
|
|
{
|
|
try {
|
|
// Get attribute IDs for Magento 2
|
|
$attributeIds = $this->getMagento2AttributeIds();
|
|
|
|
// Get categories with their attributes from Magento 1
|
|
$m1Categories = $this->getMagento1Categories($m1StoreId);
|
|
|
|
foreach ($m1Categories as $m1Category) {
|
|
if (!isset($this->categoryMapping[$m1Category->entity_id])) {
|
|
continue;
|
|
}
|
|
|
|
$m2EntityId = $this->categoryMapping[$m1Category->entity_id];
|
|
|
|
// Migrate name
|
|
if ($m1Category->name) {
|
|
$this->insertCategoryAttribute(
|
|
$m2EntityId,
|
|
$attributeIds['name'],
|
|
$m2StoreId,
|
|
$m1Category->name
|
|
);
|
|
}
|
|
|
|
// Migrate is_active
|
|
if ($m1Category->is_active !== null) {
|
|
$this->insertCategoryAttribute(
|
|
$m2EntityId,
|
|
$attributeIds['is_active'],
|
|
$m2StoreId,
|
|
$m1Category->is_active
|
|
);
|
|
}
|
|
|
|
// Migrate url_key
|
|
if ($m1Category->url_key) {
|
|
$this->insertCategoryAttribute(
|
|
$m2EntityId,
|
|
$attributeIds['url_key'],
|
|
$m2StoreId,
|
|
$m1Category->url_key
|
|
);
|
|
}
|
|
}
|
|
|
|
$this->migrationLog[] = "Migrated attributes for store mapping: M1 Store {$m1StoreId} -> M2 Store {$m2StoreId}";
|
|
|
|
} catch (Exception $e) {
|
|
Log::error("Error migrating category attributes: " . $e->getMessage());
|
|
$this->migrationLog[] = "ERROR: Failed to migrate attributes for store {$m1StoreId}: " . $e->getMessage();
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Insert category attribute value
|
|
*/
|
|
protected function insertCategoryAttribute($entityId, $attributeId, $storeId, $value)
|
|
{
|
|
// Determine which table to use based on attribute type
|
|
$attribute = DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'eav_attribute')
|
|
->where('attribute_id', $attributeId)
|
|
->first();
|
|
|
|
if (!$attribute) {
|
|
return;
|
|
}
|
|
|
|
$table = $this->magento2Prefix . 'catalog_category_entity_' . $attribute->backend_type;
|
|
|
|
// Check if value already exists
|
|
$exists = DB::connection($this->magento2Connection)
|
|
->table($table)
|
|
->where('entity_id', $entityId)
|
|
->where('attribute_id', $attributeId)
|
|
->where('store_id', $storeId)
|
|
->exists();
|
|
|
|
if ($exists) {
|
|
DB::connection($this->magento2Connection)
|
|
->table($table)
|
|
->where('entity_id', $entityId)
|
|
->where('attribute_id', $attributeId)
|
|
->where('store_id', $storeId)
|
|
->update(['value' => $value]);
|
|
} else {
|
|
DB::connection($this->magento2Connection)
|
|
->table($table)
|
|
->insert([
|
|
'attribute_id' => $attributeId,
|
|
'store_id' => $storeId,
|
|
'entity_id' => $entityId,
|
|
'value' => $value,
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get Magento 2 attribute IDs
|
|
*/
|
|
protected function getMagento2AttributeIds()
|
|
{
|
|
$entityTypeId = DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'eav_entity_type')
|
|
->where('entity_type_code', 'catalog_category')
|
|
->value('entity_type_id');
|
|
|
|
$attributes = DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'eav_attribute')
|
|
->where('entity_type_id', $entityTypeId)
|
|
->whereIn('attribute_code', ['name', 'is_active', 'url_key'])
|
|
->pluck('attribute_id', 'attribute_code')
|
|
->toArray();
|
|
|
|
return $attributes;
|
|
}
|
|
|
|
/**
|
|
* Get Magento 2 root category ID
|
|
*/
|
|
protected function getMagento2RootCategoryId()
|
|
{
|
|
// Root category is usually ID 2 in Magento 2
|
|
$rootId = DB::connection($this->magento2Connection)
|
|
->table($this->magento2Prefix . 'catalog_category_entity')
|
|
->where('level', 0)
|
|
->where('parent_id', 0)
|
|
->value('entity_id');
|
|
|
|
return $rootId ?: 2;
|
|
}
|
|
|
|
/**
|
|
* Test database connections
|
|
*/
|
|
public function testConnections()
|
|
{
|
|
$results = [
|
|
'magento1' => false,
|
|
'magento2' => false,
|
|
];
|
|
|
|
try {
|
|
DB::connection($this->magento1Connection)->select('SELECT 1');
|
|
$results['magento1'] = true;
|
|
} catch (Exception $e) {
|
|
$results['magento1_error'] = $e->getMessage();
|
|
}
|
|
|
|
try {
|
|
DB::connection($this->magento2Connection)->select('SELECT 1');
|
|
$results['magento2'] = true;
|
|
} catch (Exception $e) {
|
|
$results['magento2_error'] = $e->getMessage();
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
}
|
|
|