العقيدة ResultSetMapping مع أمثلة

يوفر Doctrine ORM للمطور وسائل ملائمة لجلب البيانات. هذا هو DQL قوي للعمل بطريقة موجهة للكائنات ، ومنشئ استعلام مناسب ، بسيط وبديهي للاستخدام. تغطي معظم الاحتياجات ، ولكن في بعض الأحيان يصبح من الضروري استخدام استعلامات SQL المحسنة أو الخاصة بنظام DBMS معين. للعمل مع نتائج الاستعلام في التعليمات البرمجية ، من المهم فهم كيفية عمل رسم الخرائط في Doctrine.



ويستند Doctrine ORM إلى نمط مخطط البيانات ، الذي يعزل التمثيل العلائقي عن الكائن ويحول البيانات بينهما. أحد العناصر الرئيسية لهذه العملية هو كائن ResultSetMapping ، الذي يصف كيفية تحويل نتائج الاستعلام من النموذج العلائقي إلى الكائن. تستخدم Doctrine دائمًا ResultSetMapping لتمثيل نتائج الاستعلام ، ولكن عادة ما يتم إنشاء هذا الكائن على أساس التعليقات التوضيحية أو yaml ، xml configs ، فإنه يبقى مخفيًا عن أعين المطور ، وبالتالي لا يعرف الجميع عن قدراته.

للحصول على أمثلة للعمل مع ResultSetMapping ، سأستخدم MySQL وقاعدة بيانات نموذج الموظفين .

هيكل DB


دعونا نصف إدارة الكيانات ، الموظف ، الراتب ، والتي سنستمر في العمل بها أكثر.

الانقسام
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Departments
 *
 * @ORM\Table(name="departments", uniqueConstraints={@ORM\UniqueConstraint(name="dept_name", columns={"dept_name"})})
 * @ORM\Entity
 */
class Department
{
    /**
     * @var string
     *
     * @ORM\Column(name="dept_no", type="string", length=4, nullable=false, options={"fixed"=true})
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $deptNo;

    /**
     * @var string
     *
     * @ORM\Column(name="dept_name", type="string", length=40, nullable=false)
     */
    private $deptName;

    /**
     * @var \Doctrine\Common\Collections\Collection
     *
     * @ORM\ManyToMany(targetEntity="Employee", mappedBy="deptNo")
     */
    private $empNo;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->empNo = new \Doctrine\Common\Collections\ArrayCollection();
    }
}



موظف

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* Employees
*
* ORM\Table(name=«employees»)
* ORM\Entity
*/
class Employee
{
/**
* var int
*
* ORM\Column(name=«emp_no», type=«integer», nullable=false)
* ORM\Id
* ORM\GeneratedValue(strategy=«IDENTITY»)
*/
private $empNo;

/**
* var \DateTime
*
* ORM\Column(name=«birth_date», type=«date», nullable=false)
*/
private $birthDate;

/**
* var string
*
* ORM\Column(name=«first_name», type=«string», length=14, nullable=false)
*/
private $firstName;

/**
* var string
*
* ORM\Column(name=«last_name», type=«string», length=16, nullable=false)
*/
private $lastName;

/**
* var string
*
* ORM\Column(name=«gender», type=«string», length=0, nullable=false)
*/
private $gender;

/**
* var \DateTime
*
* ORM\Column(name=«hire_date», type=«date», nullable=false)
*/
private $hireDate;

/**
* var \Doctrine\Common\Collections\Collection
*
* ORM\ManyToMany(targetEntity=«Department», inversedBy=«empNo»)
* ORM\JoinTable(name=«dept_manager»,
* joinColumns={
* ORM\JoinColumn(name=«emp_no», referencedColumnName=«emp_no»)
* },
* inverseJoinColumns={
* ORM\JoinColumn(name=«dept_no», referencedColumnName=«dept_no»)
* }
* )
*/
private $deptNo;

/**
* Constructor
*/
public function __construct()
{
$this->deptNo = new \Doctrine\Common\Collections\ArrayCollection();
}

}


راتب
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* Salaries
*
* ORM\Table(name=«salaries», indexes={@ORM\Index(name=«IDX_E6EEB84BA2F57F47», columns={«emp_no»})})
* ORM\Entity
*/
class Salary
{
/**
* var \DateTime
*
* ORM\Column(name=«from_date», type=«date», nullable=false)
* ORM\Id
* ORM\GeneratedValue(strategy=«NONE»)
*/
private $fromDate;

/**
* var int
*
* ORM\Column(name=«salary», type=«integer», nullable=false)
*/
private $salary;

/**
* var \DateTime
*
* ORM\Column(name=«to_date», type=«date», nullable=false)
*/
private $toDate;

/**
* var Employee
*
* ORM\Id
* ORM\OneToOne(targetEntity=«Employee»)
* ORM\JoinColumns({
* ORM\JoinColumn(name=«emp_no», referencedColumnName=«emp_no»)
* })
*/
private $empNo;

/**
* var Employee
*
*/
private $employee;

}



نتيجة الكيان


لنبدأ بقسم بسيط ، حدد جميع الأقسام من القاعدة ونعرضها على القسم:

        $rsm = new Query\ResultSetMapping();
        $rsm->addEntityResult(Department::class, 'd');
        $rsm->addFieldResult('d', 'dept_no', 'deptNo');
        $rsm->addFieldResult('d', 'dept_name', 'deptName');

        $sql = 'SELECT * FROM departments';

        $query = $this->entityManager->createNativeQuery($sql, $rsm);

        $result = $query->getResult();


تشير طريقة addEntityResult إلى الفئة التي سيتم عرض العينة عليها ، وتشير أساليب addFieldResult إلى تعيين بين أعمدة العينة وحقول الكائن. سيتم نقل استعلام SQL الذي تم تمريره إلى طريقة createNativeQuery إلى قاعدة البيانات في هذا النموذج ولن يقوم Doctrine بتعديله بأي شكل من الأشكال.

تجدر الإشارة إلى أن الكيان بالضرورة له معرف فريد يجب تضمينه في fieldResult.

تم الانضمام إلى نتيجة الكيان


نختار الأقسام التي يعمل فيها موظفون بعد بداية عام 2000 مع هؤلاء الموظفين.

        $rsm = new Query\ResultSetMapping();
        $rsm->addEntityResult(Department::class, 'd');
        $rsm->addFieldResult('d', 'dept_no', 'deptNo');
        $rsm->addFieldResult('d', 'dept_name', 'deptName');

        $rsm->addJoinedEntityResult(Employee::class, 'e', 'd', 'empNo');
        $rsm->addFieldResult('e', 'first_name', 'firstName');
        $rsm->addFieldResult('e', 'last_name', 'lastName');
        $rsm->addFieldResult('e', 'birth_date', 'birthDate');
        $rsm->addFieldResult('e', 'gender', 'gender');
        $rsm->addFieldResult('e', 'hire_date', 'hireDate');

        $sql = "
        SELECT *
            FROM departments d
            JOIN dept_emp ON d.dept_no = dept_emp.dept_no
            JOIN employees e on dept_emp.emp_no = e.emp_no
        WHERE e.hire_date > DATE ('1999-12-31')
       ";

        $query = $this->entityManager->createNativeQuery($sql, $rsm);

        $result = $query->getResult();

ResultSetMappingBuilder


كما ترى ، فإن العمل مباشرةً مع كائن ResultSetMapping معقد إلى حد ما ويجعله مفصلاً للغاية لوصف مقارنة التحديد والكائنات. بالإضافة إلى ذلك ، يوفر Doctrine أداة أكثر ملاءمة - ResultSetMappingBuilder ، وهو عبارة عن غلاف على RSM ويضيف طرقًا أكثر ملاءمة للعمل مع رسم الخرائط. على سبيل المثال، generateSelectClause طريقة ، والذي يسمح لك لخلق المعلمة بالنسبة للجزء SELECT الاستعلام مع وصف الحقول الضرورية لاختيار. يمكن إعادة كتابة الطلب السابق في شكل أبسط.

        $sql = "
        SELECT {$rsm->generateSelectClause()}
            FROM departments d
            JOIN dept_emp ON d.dept_no = dept_emp.dept_no
            JOIN employees e on dept_emp.emp_no = e.emp_no
        WHERE e.hire_date > DATE ('1999-12-31')
       ";

تجدر الإشارة إلى أنه إذا لم تحدد جميع حقول الكائن الذي تم إنشاؤه ، فسوف تُرجع Doctrine كائنًا جزئيًا ، يمكن تبرير استخدامه في بعض الحالات ، ولكن سلوكها خطير ولا يوصى به . بدلاً من ذلك ، يتيح لك ResultSetMappingBuilder عدم تحديد كل حقل في الفصل النهائي ، ولكن يمكنك استخدام الكيانات الموضحة من خلال التعليقات التوضيحية (تكوينات yaml و xml). نعيد كتابة RSM من الطلب السابق ، مع مراعاة هذه الطرق:

        $rsm = new Query\ResultSetMappingBuilder($this->entityManager);
        $rsm->addRootEntityFromClassMetadata(Department::class, 'd');
        $rsm->addJoinedEntityFromClassMetadata(Employee::class, 'e', 'd', 'empNo');

نتيجة العددية


الاستخدام الواسع النطاق للكيان الموصوف ليس له ما يبرره ، يمكن أن يؤدي إلى مشاكل في الأداء ، حيث يحتاج المذهب إلى إنشاء العديد من الكائنات التي لن يتم استخدامها بالكامل في المستقبل. في مثل هذه الحالات ، يتم توفير أداة لرسم خرائط النتيجة العددية ، addScalarResult .

اختر متوسط ​​الراتب لكل قسم:

        $rsm = new ResultSetMappingBuilder($this->entityManager);

        $rsm->addScalarResult('dept_name', 'department', 'string');
        $rsm->addScalarResult('avg_salary', 'salary', 'integer');

        $sql = "
            SELECT d.dept_name, AVG(s.salary) AS avg_salary
            FROM departments d
            JOIN dept_emp de on d.dept_no = de.dept_no
            JOIN employees e on de.emp_no = e.emp_no
            JOIN salaries s on e.emp_no = s.emp_no
            GROUP BY d.dept_name
        ";

        $query = $this->entityManager->createNativeQuery($sql, $rsm);

        $result = $query->getResult();

الوسيطة الأولى لأسلوب addScalarResult هو اسم العمود في مجموعة النتائج ، والثاني هو مفتاح مجموعة النتائج التي سيعود إليها Doctrine. المعلمة الثالثة هي نوع قيمة النتيجة. ستكون القيمة المرجعة دائمًا مصفوفة من المصفوفات.

تعيين DTO


لكن العمل مع المصفوفات ، خاصة عندما يكون لها بنية معقدة ، ليس مناسبًا جدًا. يجب أن تضع في اعتبارك أسماء المفاتيح وأنواع الحقول. لدى Doctrine DQL القدرة على استخدام DTOs بسيطة في نتائج الاختيار ، على سبيل المثال:

    SELECT NEW DepartmentSalary(d.dept_no, avg_salary) FROM

ماذا عن الاستعلام الأصلي و RSM؟ لا يوفر Doctrine إمكانات موثقة لإنشاء DTOs جديدة ، ولكن باستخدام خاصية newObjectMappings ، يمكننا تحديد الكائنات التي نريد تعيين نتائج التحديد إليها. على عكس Entity ، لن يتم التحكم في هذه الكائنات بواسطة UnitOfWork ، وليس مطلوبًا أن تكون في مساحات الأسماء المحددة في التكوين.

استكمال RSM من المثال السابق:

        $rsm->newObjectMappings['dept_name'] = [
            'className' => DepartmentSalary::class,
            'argIndex' => 0,
            'objIndex' => 0,
        ];

        $rsm->newObjectMappings['avg_salary'] = [
            'className' => DepartmentSalary::class,
            'argIndex' => 1,
            'objIndex' => 0,
        ];

يشير المفتاح في صفيف حقل newObjectMappings إلى العمود الناتج ، ولكن قيمته هي صفيف آخر يصف الكائن الذي يتم إنشاؤه. و اسم_الفئة مفتاح يحدد اسم فئة الكائن الجديد، argIndex - ترتيب الحجة في المنشئ للكائن، objIndex - ترتيب الكائن، إذا كنا نريد للحصول على العديد من الكائنات من كل صف من التحديد.

نتيجة ميتا


يتم استخدام نتيجة التعريف للحصول على بيانات تعريف الأعمدة ، مثل المفاتيح الأجنبية أو الأعمدة المميزة. لسوء الحظ ، لم أستطع أن أخرج بمثال يستند إلى قاعدة بيانات الموظفين ، لذلك يجب أن أقصر نفسي على وصف وأمثلة من الوثائق .

        $rsm = new ResultSetMapping;
        $rsm->addEntityResult('User', 'u');
        $rsm->addFieldResult('u', 'id', 'id');
        $rsm->addFieldResult('u', 'name', 'name');
        $rsm->addMetaResult('u', 'address_id', 'address_id');

        $query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);

في هذه الحالة ، باستخدام طريقة addMetaResult ، نخبر Doctine أن جدول المستخدمين يحتوي على مفتاح خارجي على العناوين ، ولكن بدلاً من تحميل الاقتران في الذاكرة (تحميل حريص) ، نقوم بإنشاء كائن وكيل يخزن معرف الكيان ، وعندما يتم الوصول إليه ، يتم تحميله لها من قاعدة البيانات.

لا تقدم قواعد البيانات العلائقية الكلاسيكية آليات وراثة الجدول ، بينما ينتشر الوراثة على نطاق واسع في نموذج الكائن. يمكن عرض نتيجة تحديدنا على التسلسل الهرمي للفئات ، وفقًا لبعض السمات ، وهي قيمة العمود <discriminator_column> في التحديد الناتج. في هذه الحالة ، يمكننا إخبار RSM بالعمود المذهب الذي يجب أن يحدد الفئة الفورية باستخدام طريقة setDiscriminatorColumn .

استنتاج


العقيدة غنية جدًا بالعديد من الميزات ، بما في ذلك تلك التي لا يعرفها جميع المطورين. في هذا المنشور ، حاولت تقديم فهم لتشغيل أحد المكونات الأساسية لـ ORM - ResultSetMapping بالاشتراك مع Native Query. إن استخدام الاستعلامات المعقدة والمحددة بالفعل للنظام الأساسي مع الحفاظ على توافر الأمثلة وفهمها سيكون مهمة صعبة ، لأن التركيز يتم على فهم عمل ResultSetMapping. بعد ذلك ، يمكنك استخدامه في استعلامات معقدة حقا لقواعد البيانات الخاصة بك.

All Articles