اتخطّى للمحتوى

المرحلة 03 · الكتالوج والتجارةدرس 14 من 3420 دقيقة قراءةآخر تحديث:

Lesson 14 / 34

Product Attributesالمرجع الكامل.

كل حاجة عن خصائص المنتجات — الأنواع والخصائص، الإنشاء من الأدمن ومن الكود، الخيارات والـ source models، القراءة والفلترة، العرض في الواجهة، وأثرها على الأداء.

● مرجع أدمن + كود Data Patch الأداء

الـ Product Attributes في Magento 2 متخزّنة بنظام EAV: تعريفها في eav_attribute و catalog_eav_attribute، وقيمها في جداول catalog_product_entity_varchar و _int و _decimal حسب النوع. الخصائص اللي الكود بيعتمد عليها تتعمل بـ Data Patch وEavSetup::addAttribute، والخصائص اللي المحتوى بيديرها من الأدمن. النوع والـ scope قرارات صعب التراجع عنها، وإعدادات الفلترة والبحث ليها تكلفة أداء.

// ليه ده موضوع كبير الخصائص هي الطريقة الأساسية لتوسيع المنتج في Magento. أي بيانات إضافية على المنتج بتتعمل كخاصية. بس كل خاصية ليها تكلفة أداء، والإعدادات الغلط بتبطّئ المتجر من غير ما تلاحظ. القسم 12 عن الأداء مهم زي أقسام الإنشاء بالظبط.
◆ Part 1 — الفهم
01

يعني إيه Attribute

الأساس اللي كل حاجة بعده بتبني عليه.

إيه هي

الـ attribute هي خاصية على المنتج — الاسم، السعر، اللون، المقاس، أي حاجة. Magento بيخزّنها بنظام EAV، يعني القيم في جداول منفصلة مش أعمدة في جدول المنتج.

لو كانت أعمدة

كل خاصية جديدة = تعديل على بنية الجدول. ومنتج ملوش لون = عمود فاضي.

مع EAV

خاصية جديدة = صف في جدول التعريف. والقيم بتتخزّن للمنتجات اللي ليها قيمة بس.

تشبيه تخيّل ملف موظفين. بدل ما تعمل جدول بمئات الأعمدة (معظمها فاضي لمعظم الناس)، بتعمل جدول "أنواع المعلومات" وجدول "القيم". كل موظف بيسجّل المعلومات اللي عنده بس. مرونة عالية، بس الوصول بيحتاج ربط أكتر.
المقايضة

مرونة عالية، قراءة أبطأ. جلب منتج كامل بيحتاج ربط جداول متعددة. عشان كده Magento عنده indexers بتحوّل البيانات لجداول مسطّحة جاهزة للقراءة السريعة.

نوعان من الخصائص
  • System attributes — جاية مع Magento (name, price, sku). مش هتقدر تحذفها.
  • User-defined — اللي بتعملها إنت. قابلة للتعديل والحذف.
i
فيه خصائص اسمها static — زي sku وcreated_at. دي متخزّنة كأعمدة عادية في جدول المنتج مش في جداول EAV، لأنها موجودة لكل منتج ومطلوبة دايماً.
• • •
02

الجداول — فين البيانات

مهم جداً وقت التشخيص.

التعريف
eav_attribute
catalog_eav_attribute
eav_attribute_option
القيم
_varchar
_int
_decimal
_text
_datetime
table
eav_attribute
التعريف الأساسي — الكود، النوع، اللابل، الـ source والـ backend model.
دوّر هنالما تعرف اسم الخاصية وعايز تشوف إعداداتها.
catalog_eav_attribute
الإعدادات الخاصة بالكتالوج — is_filterable، is_searchable، used_in_product_listing، الـ scope.
دوّر هنالما خاصية مش ظاهرة في الفلاتر أو البحث. معظم مشاكل الأداء سببها إعداد هنا.
eav_attribute_option
خيارات القوائم المنسدلة (الـ id بس).
دوّر هنالما تشتغل على خاصية select أو multiselect.
eav_attribute_option_value
نصوص الخيارات — لكل store view، فده مكان الترجمة.
دوّر هنالما خيار ظاهر بلغة غلط أو مش مترجم.
catalog_product_entity_varchar
قيم النصوص القصيرة. وفيه نظائر: _int، _decimal، _text، _datetime.
دوّر هنالما قيمة منتج معيّن غلط — كل صف فيه entity_id وattribute_id وstore_id.
eav_attribute_set / _group
مجموعات الخصائص وتنظيمها في تبويبات.
دوّر هنالما خاصية مش ظاهرة في صفحة تعديل المنتج.
استعلام تشخيصي مفيدsql
-- كل إعدادات خاصية معيّنة
SELECT a.attribute_id, a.attribute_code, a.backend_type,
       a.frontend_input, c.is_global, c.is_filterable,
       c.is_searchable, c.used_in_product_listing
FROM eav_attribute a
JOIN catalog_eav_attribute c ON c.attribute_id = a.attribute_id
WHERE a.attribute_code = 'color';
i
القيم بتتخزّن حسب backend_type. خاصية نوعها varchar قيمها في catalog_product_entity_varchar. لو دوّرت في الجدول الغلط مش هتلاقي حاجة.
• • •
03

الأنواع (input types)

أنهي نوع تختار لأي بيانات.

frontend_input
text
سطر نص واحد. النوع الخلفي varchar.
امتىأسماء، أكواد، نصوص قصيرة.
textarea
نص طويل. النوع الخلفي text.
امتىأوصاف ومحتوى طويل.
texteditor
محرّر نصوص بتنسيق (WYSIWYG).
امتىمحتوى تسويقي محتاج تنسيق HTML.
select
قائمة منسدلة — اختيار واحد. النوع الخلفي int (بيخزّن id الخيار).
امتىقيم محدودة ومعروفة — اللون، المقاس، الماركة. وده اللي بيشتغل في الفلاتر.
multiselect
اختيار متعدد. النوع الخلفي varchar (ids مفصولة بفاصلة).
امتىخصائص متعددة القيم — المميزات، الاستخدامات.
boolean
نعم / لا. النوع الخلفي int.
امتىأعلام بسيطة — "منتج مميّز"، "شحن مجاني".
price
قيمة سعرية بمعالجة العملة. النوع الخلفي decimal.
امتىأي مبلغ مالي — عشان يتعامل مع العملات صح.
date / datetime
تاريخ أو تاريخ ووقت.
امتىتواريخ الإصدار، انتهاء الصلاحية.
media_image
صورة إضافية مستقلة عن معرض الصور.
امتىصورة مخصّصة لغرض معيّن (شهادة، مخطط).
weight
وزن بمعالجة وحدات القياس.
امتىأوزان إضافية غير وزن الشحن.
swatch_visual / swatch_text
عيّنات لونية أو نصية.
امتىالألوان والمقاسات في المنتجات القابلة للتكوين.
!
القاعدة الذهبية في الاختيار: لو القيم محدودة ومعروفة، استخدم select مش text. الفرق كبير — الـ select بيشتغل في الفلاتر والبحث المنظّم، والـ text لأ. وتحويل خاصية من text لـ select بعد ما تمتلي بالبيانات عملية مؤلمة.
• • •
04

الـ Scope

أنهي مستوى تختلف عنده القيمة.

scope
Global
قيمة واحدة لكل المتاجر.
امتىبيانات ثابتة بطبيعتها — الوزن، الأبعاد، كود المصنّع.
Website
قيمة لكل website.
امتىالأسعار لما تختلف بين الأسواق (حسب إعداد نطاق السعر).
Store View
قيمة لكل store view (لغة).
امتىأي حاجة مترجمة — الاسم، الوصف، عناوين SEO.
إزاي بيتخزّن

جداول القيم فيها عمود store_id. القيمة الافتراضية بتتخزّن بـ store_id = 0، والقيم المخصّصة لكل store view بـ id بتاعها. لو مفيش قيمة مخصّصة، Magento بياخد الافتراضية.

!
اختيار الـ scope قرار صعب التراجع عنه. لو عملت خاصية global وبعدين احتجت تترجمها، التحويل لـ store view ممكن — بس البيانات الموجودة محتاجة معالجة. فكّر في الترجمة من الأول، خصوصاً لو المشروع متعدد اللغات.
الثوابت في الكودphp
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;

ScopedAttributeInterface::SCOPE_GLOBAL
ScopedAttributeInterface::SCOPE_WEBSITE
ScopedAttributeInterface::SCOPE_STORE
• • •
05

Attribute Sets و Groups

إزاي الخصائص بتتنظّم.

المستويات
Attribute Set
Group (تبويب)
Attribute
Attribute Set

مجموعة الخصائص المتاحة لنوع منتج معيّن. مثال: مجموعة "ملابس" فيها اللون والمقاس والخامة، ومجموعة "إلكترونيات" فيها الضمان والقوة.

Group

تبويب داخل صفحة تعديل المنتج. الخصائص بتتوزّع على تبويبات زي "General" و"SEO" و"Design" عشان الصفحة متبقاش فوضى.

ليه مهم

المنتج بيشوف الخصائص اللي في مجموعته بس. لو عملت خاصية جديدة وماضفتهاش لمجموعة، مش هتظهر في صفحة التعديل — وده سبب شائع لـ "عملت الخاصية ومش لاقيها".

i
فيه مجموعة اسمها Default بتيجي مع Magento. لما تضيف خاصية للمجموعة دي، كل المنتجات اللي بتستخدمها هتشوفها. وفي الأمثلة الجاية هنضيف عليها.
• • •
◆ Part 2 — الإنشاء
06

الإنشاء من الأدمن

السريع — وامتى يكون غلط.

المسار
  • Stores → Attributes → Product ثم Add New Attribute.
  • حدد Default Label وCatalog Input Type.
  • لو select أو multiselect، ضيف الخيارات.
  • من Advanced: الكود، الـ scope، هل مطلوب، القيمة الافتراضية.
  • من Storefront Properties: الفلترة، البحث، العرض في القوائم.
  • احفظ، وبعدين ضيفها لـ Attribute Set — من Attribute Set.
!
الخطوة السادسة هي اللي بتتنسي. الخاصية اتعملت بس مش هتظهر في أي منتج لحد ما تضيفها لمجموعة. ده أشهر "عملتها ومش لاقيها".

من الأدمن

سريع، بس موجود في قاعدة بيانات واحدة بس. مش هينتقل لـ staging أو production تلقائياً.

بالكود

بيتطبّق على كل بيئة مع النشر، ومسجّل في الـ git، وقابل للمراجعة.

القاعدة العملية

الخصائص اللي الكود بيعتمد عليها → بالكود. لو موديولك بيقرا warranty_months، لازم تكون متعرّفة بالكود، وإلا هيقع على أي بيئة مفيهاش. الخصائص اللي فريق المحتوى بيديرها → من الأدمن عادي.

• • •
07

الإنشاء بالكود — Data Patch

الطريقة الصح للخصائص اللي الكود بيعتمد عليها.

Setup/Patch/Data/AddWarrantyAttribute.phpphp
namespace Vendor\Module\Setup\Patch\Data;

use Magento\Catalog\Model\Product;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchRevertableInterface;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;

class AddWarrantyAttribute
    implements DataPatchInterface, PatchRevertableInterface
{
    public function __construct(
        private readonly ModuleDataSetupInterface $moduleDataSetup,
        private readonly EavSetupFactory $eavSetupFactory
    ) {}

    public function apply(): void
    {
        $this->moduleDataSetup->getConnection()->startSetup();

        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create([
            'setup' => $this->moduleDataSetup
        ]);

        $eavSetup->addAttribute(
            Product::ENTITY,
            'warranty_months',
            [
                'type'         => 'int',
                'label'        => 'Warranty (months)',
                'input'        => 'text',
                'required'     => false,
                'user_defined' => true,
                'global'       => ScopedAttributeInterface::SCOPE_GLOBAL,
                'visible'      => true,
                'searchable'   => false,
                'filterable'   => false,
                'comparable'   => false,
                'group'        => 'General',
                'sort_order'   => 100,
            ]
        );

        $this->moduleDataSetup->getConnection()->endSetup();
    }

    public function revert(): void
    {
        $eavSetup = $this->eavSetupFactory->create([
            'setup' => $this->moduleDataSetup
        ]);
        $eavSetup->removeAttribute(Product::ENTITY, 'warranty_months');
    }

    public static function getDependencies(): array
    {
        return [];
    }

    public function getAliases(): array
    {
        return [];
    }
}
التشغيل
terminalbash
bin/magento setup:upgrade
bin/magento cache:clean
!
الـ data patch بيتنفّذ مرة واحدة بس. Magento بيسجّل اسم الكلاس في جدول patch_list. لو عدّلت الـ patch بعد ما اتنفّذ، التعديل مش هيتطبّق — لازم patch جديد بكلاس جديد.
i
الـ PatchRevertableInterface اختياري بس مفيد — بيخلّي الخاصية تتشال لو الموديول اتشال. من غيره الخاصية بتفضل في قاعدة البيانات بعد إزالة الموديول.
patch للتعديلphp
public function apply(): void
{
    $eavSetup = $this->eavSetupFactory->create([
        'setup' => $this->moduleDataSetup
    ]);

    // غيّر خاصية واحدة
    $eavSetup->updateAttribute(
        Product::ENTITY,
        'warranty_months',
        'is_filterable',
        1
    );

    // أو كذا خاصية مرة واحدة
    $eavSetup->updateAttribute(
        Product::ENTITY,
        'warranty_months',
        [
            'is_filterable' => 1,
            'is_searchable' => 1,
        ]
    );
}
الإضافة لمجموعة تانية
ربط بمجموعةphp
$eavSetup->addAttributeToSet(
    Product::ENTITY,
    'Default',      // اسم الـ attribute set
    'General',      // اسم الـ group (التبويب)
    'warranty_months'
);
• • •
08

كل خصائص الإعداد

المصفوفة اللي بتتبعت لـ addAttribute — مرجع كامل.

key
type
النوع الخلفي: varchar, int, decimal, text, datetime, static.
امتىبيحدد الجدول اللي القيم هتتخزّن فيه. لازم يناسب الـ input.
input
شكل الحقل في الأدمن (من قائمة القسم 03).
امتىدايماً مطلوب.
label
الاسم المعروض.
امتىدايماً. قابل للترجمة لكل store view.
global
الـ scope (من القسم 04).
امتىدايماً. فكّر كويس — صعب التراجع.
required
هل إلزامي عند حفظ المنتج.
حذرلو خليتها true على متجر فيه منتجات، كل المنتجات القديمة مش هتتحفظ لحد ما تتملي.
user_defined
هل قابلة للتعديل والحذف من الأدمن.
امتىtrue عادة. false لو عايز تمنع الحذف بالغلط.
default
القيمة الافتراضية للمنتجات الجديدة.
امتىلما فيه قيمة منطقية شائعة.
visible
هل تظهر في صفحة تعديل المنتج.
امتىfalse للخصائص اللي الكود بيديرها بس.
searchable
هل تدخل في البحث النصي.
تكلفةبتكبّر فهرس البحث. فعّلها للحقول اللي العميل هيدوّر بيها فعلاً.
filterable
هل تظهر في الفلاتر الجانبية.
تكلفةمحتاجة فهرسة إضافية. بتشتغل مع select/multiselect بس.
used_in_product_listing
هل تتحمّل مع مجموعات المنتجات.
تكلفةبتتحمّل مع كل منتج في كل صفحة قسم. فعّلها لو هتعرضها في القوائم فقط.
visible_on_front
هل تظهر في تبويب "More Information".
امتىالمواصفات اللي العميل يهمّه يشوفها.
comparable
هل تظهر في صفحة المقارنة.
امتىالمواصفات اللي بيتقارن بيها.
unique
هل القيمة لازم تكون فريدة عبر المنتجات.
امتىأكواد داخلية أو معرّفات خارجية.
source
كلاس بيحدد الخيارات المتاحة.
امتىselect بخيارات ديناميكية (القسم 09).
backend
كلاس بيعالج القيمة قبل الحفظ وبعد التحميل.
امتىإلزامي للـ multiselect — بيحوّل المصفوفة لنص.
apply_to
أنواع المنتجات اللي تنطبق عليها.
امتىلو الخاصية تخص نوع معيّن — مثلاً simple,configurable.
is_used_in_grid
هل متاحة كعمود في جدول المنتجات بالأدمن.
امتىلما فريق المحتوى محتاج يشوفها أو يفلتر بيها.
!
التلاتة المعلّمين بالكهرماني (searchable, filterable, used_in_product_listing) هم أكبر مصدر لمشاكل الأداء في الكتالوج. القسم 12 بيفصّلهم.
• • •
09

الخيارات و Source Models

للقوائم المنسدلة — ثابتة أو ديناميكية.

خاصية select بخيارات محددةphp
$eavSetup->addAttribute(
    Product::ENTITY,
    'material',
    [
        'type'   => 'int',
        'label'  => 'Material',
        'input'  => 'select',
        'source' => Table::class,  // الافتراضي للخيارات
        'global' => ScopedAttributeInterface::SCOPE_GLOBAL,
        'option' => [
            'values' => ['Cotton', 'Leather', 'Synthetic'],
        ],
        'group'  => 'General',
    ]
);
i
الخيارات بتتخزّن في قاعدة البيانات، فالأدمن يقدر يضيف ويعدّل عليها بعدين. مناسب للقيم اللي فريق المحتوى بيديرها.
امتى تحتاجه

لما الخيارات محسوبة مش ثابتة — جاية من نظام خارجي، أو من جدول تاني، أو من إعدادات. الـ source model بيولّدها في كل مرة.

Model/Attribute/Source/Supplier.phpphp
namespace Vendor\Module\Model\Attribute\Source;

use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;

class Supplier extends AbstractSource
{
    public function __construct(
        private readonly SupplierRepository $repository
    ) {}

    public function getAllOptions(): array
    {
        if ($this->_options === null) {
            $this->_options = [
                ['value' => '', 'label' => __('-- Select --')],
            ];

            foreach ($this->repository->getActive() as $supplier) {
                $this->_options[] = [
                    'value' => $supplier->getCode(),
                    'label' => $supplier->getName(),
                ];
            }
        }

        return $this->_options;
    }
}
!
الـ getAllOptions() بيتنده كتير — في الأدمن وفي الواجهة ومع كل منتج في القوائم. لو بيعمل استعلام أو نداء خارجي، لازم تخزّن النتيجة زي ما في المثال ($this->_options)، ويفضّل cache حقيقي لو الخيارات جاية من برّه.
multiselect — لاحظ الـ backendphp
$eavSetup->addAttribute(
    Product::ENTITY,
    'features',
    [
        'type'    => 'varchar',       // مش int!
        'label'   => 'Features',
        'input'   => 'multiselect',
        'source'  => Table::class,
        // إلزامي: بيحوّل المصفوفة لنص مفصول بفواصل
        'backend' => ArrayBackend::class,
        'global'  => ScopedAttributeInterface::SCOPE_GLOBAL,
        'option'  => [
            'values' => ['Waterproof', 'Wireless', 'Foldable'],
        ],
    ]
);
!
حاجتين لازم تتظبطوا في الـ multiselect: النوع varchar مش int (لأنه بيخزّن كذا id في نص واحد)، وArrayBackend إلزامي. من غيرهم الحفظ بيفشل أو القيم بتتخزّن غلط.
• • •
◆ Part 3 — الاستخدام
10

القراءة والكتابة

إزاي توصل للقيم في الكود.

method
$product->getData('code')
القيمة الخام — الـ id للـ select.
امتىالمنطق والمقارنات. الأوضح والأصرح.
$product->getWarrantyMonths()
نفس النتيجة بـ magic getter.
امتىيشتغل، بس أقل وضوحاً والـ IDE مش بيساعد فيه.
$product->getAttributeText('color')
النص المعروض بدل الـ id.
امتىعرض قيمة select للمستخدم.
$product->getCustomAttribute('code')
بيرجّع كائن فيه getValue()، أو null.
امتىالشغل مع service contracts والـ APIs.
$product->getResource()->getAttribute('code')
كائن الخاصية نفسها — للوصول لإعداداتها.
امتىتحتاج تعرف نوعها أو خياراتها برمجياً.
أمثلة عمليةphp
// قيمة بسيطة
$months = (int) $product->getData('warranty_months');

// select — الـ id مقابل النص
$colorId   = $product->getData('color');        // 42
$colorText = $product->getAttributeText('color'); // 'Red'

// multiselect — نص مفصول، حوّله لمصفوفة
$raw = $product->getData('features');   // '12,15,18'
$ids = $raw ? explode(',', $raw) : [];

// آمن مع القيم الفاضية
$attr = $product->getCustomAttribute('warranty_months');
$value = $attr ? $attr->getValue() : null;
!
لو حمّلت المنتج من collection، الخاصية مش هتكون موجودة إلا لو طلبتها صراحةً بـ addAttributeToSelect(). الـ getData() هيرجّع null من غير أي خطأ — وده سبب متكرر لـ "القيمة موجودة في الأدمن ومش ظاهرة في الكود".
الحفظ الصحphp
// منتج واحد
$product = $this->productRepository->get('SKU-123');
$product->setData('warranty_months', 24);
$this->productRepository->save($product);

// multiselect — مصفوفة، والـ backend بيحوّلها
$product->setData('features', [12, 15]);
تحديث جماعي — أسرع بكتيرphp
use Magento\Catalog\Model\Product\Action;

// بيحدّث الخاصية بس، من غير حفظ المنتج كامل
$this->productAction->updateAttributes(
    [1, 2, 3],                  // product ids
    ['warranty_months' => 24],  // القيم
    0                            // store id
);
i
الـ updateAttributes أسرع بمراحل من حفظ كل منتج على حدة، لأنه بيتخطى تحميل المنتج كامل وإعادة فهرسة كل حاجة. استخدمه في أي تحديث جماعي — الفرق ممكن يكون ساعات على كتالوج كبير.
• • •
11

الفلترة في الـ Collections

البحث بالخصائص.

Collection — للكود الداخليphp
$collection = $this->collectionFactory->create();

$collection
    // لازم تطلب الخاصية عشان تقدر تقراها
    ->addAttributeToSelect(['name', 'warranty_months'])
    ->addAttributeToFilter('warranty_months', ['gteq' => 12])
    ->addAttributeToFilter('status', 1)
    ->setPageSize(20);

foreach ($collection as $product) {
    $months = $product->getData('warranty_months');
}
معاملات الفلترة
operator
eq / neq
يساوي / لا يساوي.
امتىالمطابقة المباشرة.
gt / gteq / lt / lteq
أكبر / أكبر أو يساوي / أصغر / أصغر أو يساوي.
امتىالأرقام والتواريخ.
like / nlike
بحث جزئي — محتاج %.
امتىالبحث النصي الجزئي.
in / nin
ضمن قائمة / خارجها.
امتىالفلترة بمجموعة قيم.
null / notnull
فاضي / مش فاضي.
امتىالمنتجات اللي ماتملتش.
finset
بحث داخل قيمة multiselect.
امتىإلزامي للـ multiselect — الـ eq مش هيشتغل معاه.
Repository — للـ APIs والكود الحديثphp
$criteria = $this->searchCriteriaBuilder
    ->addFilter('warranty_months', 12, 'gteq')
    ->addFilter('status', 1)
    ->create();

$result = $this->productRepository->getList($criteria);

foreach ($result->getItems() as $product) {
    // ...
}
!
متستخدمش addAttributeToSelect('*') على كتالوج كبير. بيحمّل كل خاصية لكل منتج، وده بيعمل استعلامات ضخمة وبيستهلك ذاكرة رهيبة. اطلب اللي محتاجه بس.
• • •
◆ Part 4 — الأداء والخلاصة
12

الأداء

الجزء اللي بيفرّق بين كتالوج سريع وبطيء.

ليه ده مهم

كل خاصية بتضيف صفوف وربط جداول. على كتالوج كبير، إعداد واحد غلط ممكن يضيف ثواني على وقت تحميل صفحة القسم — والأثر مش بيبان في التطوير لأن البيانات قليلة.

setting · cost
used_in_product_listing = 1
بتتحمّل مع كل منتج في كل صفحة قسم.
الأغلىفعّلها فقط لو هتعرضها فعلاً في القوائم. دي أكتر واحدة بتتفعّل بالغلط.
is_filterable = 1
بتحتاج فهرسة إضافية وجداول فلترة.
غاليللخصائص اللي العميل بيفلتر بيها فعلاً. وبتشتغل مع select بس.
is_searchable = 1
بتكبّر فهرس البحث وبتبطّئ إعادة الفهرسة.
غاليللحقول النصية المفيدة في البحث بس.
is_used_in_grid = 1
بتضيف عمود في فهرس جدول الأدمن.
متوسطبيأثر على الأدمن بس، مش على العملاء.
عدد الخصائص إجمالاً
كل خاصية = صف إضافي لكل منتج في جدول القيم.
تراكمي200 خاصية × 50 ألف منتج = 10 مليون صف. راجع الخصائص غير المستخدمة دورياً.
فحص الإعدادات المكلّفةsql
-- الخصائص المحمّلة في القوائم
SELECT a.attribute_code, c.used_in_product_listing,
       c.is_filterable, c.is_searchable
FROM eav_attribute a
JOIN catalog_eav_attribute c ON c.attribute_id = a.attribute_id
WHERE c.used_in_product_listing = 1
   OR c.is_filterable = 1
ORDER BY a.attribute_code;

-- خصائص user-defined مالهاش أي قيم (مرشّحة للحذف)
SELECT a.attribute_code
FROM eav_attribute a
LEFT JOIN catalog_product_entity_varchar v
       ON v.attribute_id = a.attribute_id
WHERE a.entity_type_id = 4
  AND a.is_user_defined = 1
GROUP BY a.attribute_code
HAVING COUNT(v.value_id) = 0;
i
الاستعلام الأول مفيد في أي تدقيق أداء. لو لقيت خصائص كتير بـ used_in_product_listing = 1 وهي مش معروضة في القوائم، ده كسب سريع — تعطيلها بيخفّف كل صفحة قسم فوراً.
!
بعد أي تغيير في الإعدادات دي، لازم إعادة فهرسة ومسح الكاش. من غيرها التغيير مش هيبان، وممكن تفتكر إنه مانفعش.
• • •
13

الفخاخ الشائعة

اقرا دي حتى لو مش هتقرا حاجة تانية.

  • نسيان addAttributeToSelect — القيمة موجودة في الأدمن وnull في الكود. أشهر فخ على الإطلاق.
  • نسيان إضافة الخاصية لـ attribute set — مش هتظهر في صفحة المنتج خالص.
  • تعديل data patch بعد ما اتنفّذ — مش هيتطبّق. اعمل patch جديد.
  • multiselect من غير ArrayBackend — الحفظ بيفشل أو القيم بتتخزّن غلط.
  • فلترة multiselect بـ eq — لازم finset.
  • required = true على متجر فيه منتجات — كل المنتجات القديمة مش هتتحفظ.
  • اختيار text بدل select لقيم محدودة — مفيش فلاتر، والتحويل بعدين مؤلم.
  • الـ scope الغلط — خاصية global محتاجة ترجمة. صعب التراجع.
  • source model بينده قاعدة بيانات من غير تخزين — استعلام مع كل منتج في كل صفحة.
  • addAttributeToSelect('*') — بيحمّل كل حاجة ويقتل الذاكرة.
i
لو خاصية مش شغّالة زي ما متوقّع، اسأل بالترتيب: متعرّفة صح؟مضافة لـ attribute set؟مطلوبة في الـ collection؟الفهرسة والكاش اتعملوا؟ الأربعة دول بيحلّوا أغلب الحالات.
• • •
14

أسئلة الإنترفيو

الأسئلة المتكررة.

Q1يعني إيه EAV وليه Magento بيستخدمه للمنتجات؟
الإجابة: نموذج بيخزّن الخصائص كصفوف في جداول منفصلة بدل أعمدة. السبب إن المنتجات بتختلف خصائصها جذرياً — كتاب مالوش مقاس وتيشيرت مالوش عدد صفحات. المقايضة: مرونة عالية مقابل قراءة أبطأ، وده اللي الـ indexers بتعوّضه.
Q2إزاي تضيف خاصية بالكود؟
الإجابة: بـ Data Patch يطبّق DataPatchInterface، وجوّاه EavSetup::addAttribute() بالإعدادات. ده بيضمن إنها تتطبّق على كل بيئة مع النشر ومسجّلة في الـ git — على عكس الإنشاء من الأدمن اللي بيفضل في قاعدة بيانات واحدة.
Q3إيه الفرق بين source model و backend model؟
الإجابة: الـ source بيحدد الخيارات المتاحة لخاصية select. الـ backend بيعالج القيمة نفسها قبل الحفظ وبعد التحميل — زي ArrayBackend اللي بيحوّل مصفوفة الـ multiselect لنص.
Q4ليه خاصية ممكن تكون null في الكود وهي موجودة في الأدمن؟
الإجابة: غالباً المنتج اتحمّل من collection من غير addAttributeToSelect(). الـ collections بتحمّل الخصائص المطلوبة صراحةً بس، للأداء. الأسباب التانية: الخاصية مش في الـ attribute set، أو القيمة متحددة على store view تاني.
Q5إزاي تضيف خاصية للفلاتر الجانبية؟
الإجابة: لازم تكون select أو multiselect، وتحدد is_filterable = 1، وبعدها إعادة فهرسة ومسح كاش. الخصائص النصية مابتشتغلش في الفلاتر — وده سبب شائع للحيرة.
Q6إزاي تحدّث خاصية لآلاف المنتجات بكفاءة؟
الإجابة: بـ Product\Action::updateAttributes() مش بحفظ كل منتج. بيحدّث الخاصية مباشرة من غير تحميل المنتج كامل ولا إعادة فهرسة كل حاجة — الفرق ممكن يكون ساعات على كتالوج كبير.

Product Attributes ✓

دلوقتي عندك الصورة كاملة: البنية والجداول، الأنواع والـ scope، الإنشاء من الأدمن وبالكود، الخيارات والـ source models، القراءة والفلترة، وأثر كل إعداد على الأداء.

الخلاصة: الخصائص اللي الكود بيعتمد عليها تتعمل بـ data patch، والنوع والـ scope قرارات صعب التراجع عنها، وكل إعداد فلترة أو بحث له تكلفة حقيقية.

// magento 2 · product attributes · eav · reference

خلّصت الدرس؟علّمه عشان تتابع تقدّمك في الكورس.

كاتب الدرس

Abdulrahman Masoud

عندك سؤال على الدرس أو محتاج مساعدة في مشروع Magento؟ كلّمني.

أسئلة شائعة

إزاي أضيف Product Attribute بالكود في Magento 2؟

بـ Data Patch في Setup/Patch/Data/ بيطبّق DataPatchInterface، وجوّاه EavSetup::addAttribute(Product::ENTITY, 'warranty_months', [...]) بالنوع والـ input والـ label والـ scope والـ group. بعدها setup:upgrade. الـ patch بيتنفّذ مرة واحدة وبيتسجّل في جدول patch_list، فلو عدّلته بعد التنفيذ مش هيتطبّق تاني ولازم patch جديد.

ليه قيمة الخاصية null في الكود وهي موجودة في الأدمن؟

غالبًا المنتج اتحمّل من collection من غير addAttributeToSelect(). الـ collections بتحمّل الخصائص المطلوبة صراحةً بس للأداء، وgetData() بيرجّع null من غير أي خطأ. الأسباب التانية: الخاصية مش مضافة للـ attribute set، أو القيمة متحددة على store view تاني، أو الكاش والفهرسة مااتعملوش بعد التغيير.

عملت Attribute ومش لاقيها في صفحة المنتج، ليه؟

لأن المنتج بيشوف الخصائص اللي في الـ attribute set بتاعه بس. لازم تضيفها لمجموعة من Stores › Attributes › Attribute Set، أو بالكود بـ addAttributeToSet(Product::ENTITY, 'Default', 'General', 'code'). لو مرّرت مفتاح group في addAttribute، الـ EavSetup بيضيفها لكل الـ attribute sets الموجودة تلقائيًا.

إيه الفرق بين Source Model و Backend Model في Magento؟

الـ source model بيحدد الخيارات المتاحة لخاصية select أو multiselect، وبيورّث من AbstractSource ويرجّعها من getAllOptions(). الـ backend model بيعالج القيمة نفسها قبل الحفظ وبعد التحميل، زي ArrayBackend اللي بيحوّل مصفوفة الـ multiselect لنص مفصول بفواصل. الـ multiselect لازم يكون نوعه varchar ومعاه ArrayBackend وإلا الحفظ بيبوظ.

إزاي أفلتر Collection بخاصية multiselect؟

بمعامل finset مش eq، لأن القيمة متخزّنة كنص فيه أكتر من id مفصولين بفاصلة: addAttributeToFilter('features', ['finset' => 12]). باقي المعاملات زي eq و gteq و like و in و null بتشتغل مع الخصائص العادية. ومتستخدمش addAttributeToSelect('*') على كتالوج كبير لأنه بيحمّل كل خاصية لكل منتج.

إزاي أعرف الخصائص اللي بتبطّئ الكتالوج في Magento 2؟

اعمل JOIN بين eav_attribute و catalog_eav_attribute وشوف اللي عليها used_in_product_listing أو is_filterable أو is_searchable. الأولى بتتحمّل مع كل منتج في كل صفحة قسم وهي أكتر واحدة بتتفعّل بالغلط، والفلترة والبحث بيكبّروا الفهارس. عطّل اللي مش مستخدم فعلًا وبعدها أعد الفهرسة وامسح الكاش.

إزاي أحدّث خاصية لآلاف المنتجات بسرعة؟

بـ Magento\Catalog\Model\Product\Action::updateAttributes($productIds, ['warranty_months' => 24], $storeId) بدل حفظ كل منتج على حدة. بيحدّث الخاصية مباشرة من غير تحميل المنتج كامل ولا تشغيل كل الـ observers والفهرسة لكل حفظة، والفرق على كتالوج كبير ممكن يكون ساعات.