PHP में उचित रिपोजिटरी पैटर्न डिज़ाइन?


291

प्रस्तावना: मैं रिलेशनल डेटाबेस के साथ MVC आर्किटेक्चर में रिपॉजिटरी पैटर्न का उपयोग करने का प्रयास कर रहा हूं।

मैंने हाल ही में PHP में TDD सीखना शुरू किया है, और मुझे एहसास हो रहा है कि मेरा डेटाबेस मेरे बाकी एप्लिकेशन के साथ बहुत निकटता से जुड़ा हुआ है। मैंने रिपॉजिटरी के बारे में पढ़ा है और अपने नियंत्रकों में इसे "इंजेक्ट" करने के लिए एक आईओसी कंटेनर का उपयोग किया है। बहुत मस्त माल। लेकिन अब रिपॉजिटरी डिज़ाइन के बारे में कुछ व्यावहारिक प्रश्न हैं। अनुसरण उदाहरण पर विचार करें।

<?php

class DbUserRepository implements UserRepositoryInterface
{
    protected $db;

    public function __construct($db)
    {
        $this->db = $db;
    }

    public function findAll()
    {
    }

    public function findById($id)
    {
    }

    public function findByName($name)
    {
    }

    public function create($user)
    {
    }

    public function remove($user)
    {
    }

    public function update($user)
    {
    }
}

अंक # 1: बहुत सारे क्षेत्र

इन सभी विधियों को चुनिंदा सभी फ़ील्ड ( SELECT *) दृष्टिकोण का उपयोग करें । हालाँकि, मेरे ऐप्स में, मैं हमेशा अपने द्वारा प्राप्त किए गए फ़ील्ड की संख्या को सीमित करने की कोशिश कर रहा हूं, क्योंकि यह अक्सर ओवरहेड जोड़ता है और चीजों को धीमा कर देता है। इस पैटर्न का उपयोग करने वालों के लिए, आप इससे कैसे निपटते हैं?

अंक # 2: बहुत सारी विधियाँ

जबकि यह वर्ग अभी अच्छा लग रहा है, मुझे पता है कि एक वास्तविक दुनिया के ऐप में मुझे बहुत अधिक तरीकों की आवश्यकता है। उदाहरण के लिए:

  • findAllByNameAndStatus
  • findAllInCountry
  • findAllWithEmailAddressSet
  • findAllByAgeAndGender
  • findAllByAgeAndGenderOrderByAge
  • आदि।

जैसा कि आप देख सकते हैं, संभावित तरीकों की एक बहुत, बहुत लंबी सूची हो सकती है। और फिर यदि आप ऊपर के क्षेत्र चयन मुद्दे में जोड़ते हैं, तो समस्या बिगड़ जाती है। अतीत में मैं सामान्य रूप से अपने नियंत्रक में सिर्फ यही तर्क देता हूँ:

<?php

class MyController
{
    public function users()
    {
        $users = User::select('name, email, status')
            ->byCountry('Canada')->orderBy('name')->rows();

        return View::make('users', array('users' => $users));
    }
}

मेरे रिपॉजिटरी दृष्टिकोण के साथ, मैं इसे समाप्त नहीं करना चाहता:

<?php

class MyController
{
    public function users()
    {
        $users = $this->repo->get_first_name_last_name_email_username_status_by_country_order_by_name('Canada');

        return View::make('users', array('users' => $users))
    }

}

अंक # 3: इंटरफ़ेस से मिलान करना असंभव है

मैं रिपॉजिटरी के लिए इंटरफेस का उपयोग करने में लाभ देखता हूं, इसलिए मैं अपने कार्यान्वयन (परीक्षण उद्देश्यों या अन्य के लिए) को स्वैप कर सकता हूं। इंटरफेस की मेरी समझ यह है कि वे एक अनुबंध को परिभाषित करते हैं जिसे एक कार्यान्वयन का पालन करना चाहिए। यह बहुत अच्छा है जब तक आप अपनी रिपॉजिटरी जैसी अतिरिक्त विधियों को जोड़ना शुरू नहीं करते हैं findAllInCountry()। अब मुझे अपने इंटरफ़ेस को अपडेट करने की भी आवश्यकता है ताकि यह विधि हो, अन्यथा, अन्य कार्यान्वयन में यह नहीं हो सकता है, और इससे मेरा आवेदन टूट सकता है। इससे पागल लगता है ... पूंछ का एक मामला कुत्ते को घेरने का।

विशिष्टता पैटर्न?

इस सुराग मुझे विश्वास है कि भंडार केवल तरीकों की एक निश्चित संख्या (तरह होना चाहिए save(), remove(), find(), findAll(), आदि)। लेकिन फिर मैं विशिष्ट लुकअप कैसे चलाते हैं? मैंने विनिर्देशन पैटर्न के बारे में सुना है , लेकिन यह मुझे लगता है कि यह केवल अभिलेखों के एक पूरे सेट को कम करता है (के माध्यम से IsSatisfiedBy()), जिसमें स्पष्ट रूप से प्रमुख प्रदर्शन मुद्दे हैं यदि आप एक डेटाबेस से खींच रहे हैं।

मदद?

स्पष्ट रूप से, मुझे रिपॉजिटरी के साथ काम करते समय चीजों को थोड़ा पुनर्विचार करने की आवश्यकता है। किसी को भी यह कैसे सबसे अच्छा संभाला है पर प्रकाश डाल सकते हैं?

जवाबों:


208

मुझे लगा कि मैं अपने प्रश्न का उत्तर देने में दरार डालूंगा। मेरे मूल प्रश्न 1-3 में मुद्दों को हल करने का सिर्फ एक तरीका निम्नलिखित है।

अस्वीकरण: मैं हमेशा पैटर्न या तकनीकों का वर्णन करते समय सही शब्दों का उपयोग नहीं कर सकता। उसके लिए खेद है।

लक्ष्य:

  • देखने और संपादन के लिए एक बुनियादी नियंत्रक का एक पूरा उदाहरण बनाएं Users
  • सभी कोड पूरी तरह से परीक्षण योग्य और नकली होना चाहिए।
  • नियंत्रक को पता नहीं होना चाहिए कि डेटा कहाँ संग्रहीत है (मतलब इसे बदला जा सकता है)।
  • SQL कार्यान्वयन (सबसे सामान्य) दिखाने के लिए उदाहरण।
  • अधिकतम प्रदर्शन के लिए, नियंत्रकों को केवल उन डेटा को प्राप्त करना चाहिए जिनकी उन्हें आवश्यकता है - कोई अतिरिक्त फ़ील्ड नहीं।
  • विकास में आसानी के लिए कार्यान्वयन को कुछ प्रकार के डेटा मैपर का लाभ उठाना चाहिए।
  • कार्यान्वयन में जटिल डेटा लुकअप करने की क्षमता होनी चाहिए।

समाधान

मैं अपने निरंतर भंडारण (डेटाबेस) इंटरैक्शन को दो श्रेणियों में विभाजित कर रहा हूं: आर (पढ़ें) और सीयूडी (क्रिएट, अपडेट, डिलीट)। मेरा अनुभव यह है कि वास्तव में पढ़ता है कि क्या एक अनुप्रयोग को धीमा करने का कारण बनता है। और जबकि डेटा हेरफेर (CUD) वास्तव में धीमा है, यह बहुत कम बार होता है, और इसलिए यह बहुत कम चिंता का विषय है।

CUD (क्रिएट, अपडेट, डिलीट) आसान है। इसमें वास्तविक मॉडलों के साथ काम करना शामिल होगा , जो तब Repositoriesदृढ़ता के लिए मेरे पास भेजे जाते हैं। ध्यान दें, मेरी रिपॉजिटरी अभी भी एक रीड मेथड उपलब्ध कराएगी, लेकिन केवल ऑब्जेक्ट निर्माण के लिए, प्रदर्शन नहीं। उस पर और बाद में।

आर (पढ़ें) इतना आसान नहीं है। यहां कोई मॉडल नहीं, बस वस्तुओं को महत्व देते हैंयदि आप चाहें तो सरणियों का उपयोग करें । ये ऑब्जेक्ट एकल मॉडल या कई मॉडल के मिश्रण का प्रतिनिधित्व कर सकते हैं, वास्तव में कुछ भी। ये अपने आप में बहुत दिलचस्प नहीं हैं, लेकिन वे कैसे उत्पन्न होते हैं। मैं जो बुला रहा हूं उसका उपयोग कर रहा हूं Query Objects

कोड:

उपयोगकर्ता मॉडल

आइए हम अपने मूल उपयोगकर्ता मॉडल के साथ सरल शुरुआत करें। ध्यान दें कि कोई भी ORM विस्तार या डेटाबेस सामान नहीं है। बस शुद्ध मॉडल महिमा। जो भी हो, अपना गेटअप, सेटर, वेलिडेशन जोड़ें।

class User
{
    public $id;
    public $first_name;
    public $last_name;
    public $gender;
    public $email;
    public $password;
}

रिपोजिटरी इंटरफ़ेस

अपना उपयोगकर्ता रिपॉजिटरी बनाने से पहले, मैं अपना रिपॉजिटरी इंटरफ़ेस बनाना चाहता हूं। यह "अनुबंध" को परिभाषित करेगा जो मेरे नियंत्रक द्वारा उपयोग किए जाने के लिए रिपॉजिटरी का पालन करना होगा। याद रखें, मेरे नियंत्रक को नहीं पता होगा कि डेटा वास्तव में कहाँ संग्रहीत है।

ध्यान दें कि मेरी रिपॉजिटरी में केवल ये तीन विधियाँ होंगी। save()विधि दोनों बनाने और उपयोगकर्ताओं को अद्यतन करने के लिए, बस या नहीं, उपयोगकर्ता वस्तु एक आईडी सेट है पर निर्भर करता है के लिए जिम्मेदार है।

interface UserRepositoryInterface
{
    public function find($id);
    public function save(User $user);
    public function remove(User $user);
}

एसक्यूएल रिपोजिटरी कार्यान्वयन

अब इंटरफ़ेस का मेरा कार्यान्वयन बनाने के लिए। जैसा कि उल्लेख किया गया है, मेरा उदाहरण एक SQL डेटाबेस के साथ होने वाला था। दोहराए जाने वाले SQL प्रश्नों को लिखने से रोकने के लिए डेटा मैपर के उपयोग पर ध्यान दें ।

class SQLUserRepository implements UserRepositoryInterface
{
    protected $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function find($id)
    {
        // Find a record with the id = $id
        // from the 'users' table
        // and return it as a User object
        return $this->db->find($id, 'users', 'User');
    }

    public function save(User $user)
    {
        // Insert or update the $user
        // in the 'users' table
        $this->db->save($user, 'users');
    }

    public function remove(User $user)
    {
        // Remove the $user
        // from the 'users' table
        $this->db->remove($user, 'users');
    }
}

क्वेरी ऑब्जेक्ट इंटरफ़ेस

अब CUD (क्रिएट, अपडेट, डिलीट) के साथ हमारे रिपॉजिटरी द्वारा ध्यान रखा गया है, हम आर (रीड) पर ध्यान केंद्रित कर सकते हैं । क्वेरी ऑब्जेक्ट केवल कुछ प्रकार के डेटा लुकअप लॉजिक का एक एनकैप्सुलेशन है। वे क्वेरी बिल्डरों नहीं हैं । इसे हमारे रिपॉजिटरी की तरह अमूर्त करके हम इसे लागू कर सकते हैं और इसे आसान बना सकते हैं। क्वेरी ऑब्जेक्ट का एक उदाहरण AllUsersQueryया AllActiveUsersQuery, या यहां तक ​​कि हो सकता है MostCommonUserFirstNames

आप सोच रहे होंगे "क्या मैं उन प्रश्नों के लिए अपनी रिपॉजिटरी में सिर्फ तरीके नहीं बना सकता?" हां, लेकिन यहां मैं ऐसा क्यों नहीं कर रहा हूं:

  • मेरे रिपॉजिटरी मॉडल ऑब्जेक्ट्स के साथ काम करने के लिए हैं। एक वास्तविक दुनिया ऐप में, मुझे passwordअपने सभी उपयोगकर्ताओं को सूचीबद्ध करने के लिए फ़ील्ड की आवश्यकता क्यों होगी ?
  • रिपॉजिटरी अक्सर मॉडल विशिष्ट होते हैं, फिर भी प्रश्नों में अक्सर एक से अधिक मॉडल शामिल होते हैं। तो आप किस विधि को अपनाते हैं?
  • यह मेरी रिपॉजिटरी को बहुत सरल रखता है - विधियों का एक फूला हुआ वर्ग नहीं।
  • सभी प्रश्न अब अपनी कक्षाओं में आयोजित किए जाते हैं।
  • वास्तव में, इस बिंदु पर, रिपॉजिटरी मेरे डेटाबेस लेयर को अमूर्त करने के लिए मौजूद हैं।

मेरे उदाहरण के लिए मैं "AllUsers" देखने के लिए एक क्वेरी ऑब्जेक्ट बनाऊंगा। यहाँ इंटरफ़ेस है:

interface AllUsersQueryInterface
{
    public function fetch($fields);
}

क्वेरी ऑब्जेक्ट कार्यान्वयन

यह वह जगह है जहां हम विकास को गति देने में मदद करने के लिए फिर से डेटा मैपर का उपयोग कर सकते हैं। ध्यान दें कि मैं लौटे हुए डेटासेट - फ़ील्ड्स में से एक ट्वीक की अनुमति दे रहा हूं। यह तब तक के बारे में है जब तक मैं प्रदर्शन की गई क्वेरी में हेरफेर करना चाहता हूं। याद रखें, मेरी क्वेरी ऑब्जेक्ट क्वेरी बिल्डर्स नहीं हैं। वे बस एक विशिष्ट क्वेरी करते हैं। हालाँकि, जब से मुझे पता है कि मैं शायद इस एक का उपयोग कर रहा हूं, विभिन्न स्थितियों में, मैं खुद को खेतों को निर्दिष्ट करने की क्षमता दे रहा हूं। मैं उन क्षेत्रों को कभी वापस नहीं करना चाहता, जिनकी मुझे आवश्यकता नहीं है!

class AllUsersQuery implements AllUsersQueryInterface
{
    protected $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function fetch($fields)
    {
        return $this->db->select($fields)->from('users')->orderBy('last_name, first_name')->rows();
    }
}

नियंत्रक पर जाने से पहले, मैं एक और उदाहरण दिखाना चाहता हूं कि यह कितना शक्तिशाली है। शायद मेरे पास एक रिपोर्टिंग इंजन है और इसके लिए एक रिपोर्ट बनाने की आवश्यकता है AllOverdueAccounts। यह मेरे डेटा मैपर के साथ मुश्किल हो सकता है, और मैं SQLइस स्थिति में कुछ वास्तविक लिखना चाह सकता हूं । कोई समस्या नहीं है, यहाँ यह क्वेरी ऑब्जेक्ट क्या दिख सकता है:

class AllOverdueAccountsQuery implements AllOverdueAccountsQueryInterface
{
    protected $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function fetch()
    {
        return $this->db->query($this->sql())->rows();
    }

    public function sql()
    {
        return "SELECT...";
    }
}

यह अच्छी तरह से एक ही वर्ग में इस रिपोर्ट के लिए मेरे सभी तर्क रखता है, और यह परीक्षण करना आसान है। मैं इसे अपने दिल की सामग्री के लिए मज़ाक कर सकता हूं, या यहां तक ​​कि पूरी तरह से एक अलग कार्यान्वयन का उपयोग कर सकता हूं।

नियंत्रक

अब मजेदार हिस्सा है - सभी टुकड़ों को एक साथ लाना। ध्यान दें कि मैं निर्भरता इंजेक्शन का उपयोग कर रहा हूं। आमतौर पर निर्भरता को कंस्ट्रक्टर में इंजेक्ट किया जाता है, लेकिन मैं वास्तव में उन्हें अपने नियंत्रक विधियों (मार्गों) में इंजेक्ट करना पसंद करता हूं। यह नियंत्रक के ऑब्जेक्ट ग्राफ को कम करता है, और मुझे वास्तव में यह अधिक सुपाठ्य लगता है। ध्यान दें, यदि आपको यह तरीका पसंद नहीं है, तो बस पारंपरिक कंस्ट्रक्टर विधि का उपयोग करें।

class UsersController
{
    public function index(AllUsersQueryInterface $query)
    {
        // Fetch user data
        $users = $query->fetch(['first_name', 'last_name', 'email']);

        // Return view
        return Response::view('all_users.php', ['users' => $users]);
    }

    public function add()
    {
        return Response::view('add_user.php');
    }

    public function insert(UserRepositoryInterface $repository)
    {
        // Create new user model
        $user = new User;
        $user->first_name = $_POST['first_name'];
        $user->last_name = $_POST['last_name'];
        $user->gender = $_POST['gender'];
        $user->email = $_POST['email'];

        // Save the new user
        $repository->save($user);

        // Return the id
        return Response::json(['id' => $user->id]);
    }

    public function view(SpecificUserQueryInterface $query, $id)
    {
        // Load user data
        if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
            return Response::notFound();
        }

        // Return view
        return Response::view('view_user.php', ['user' => $user]);
    }

    public function edit(SpecificUserQueryInterface $query, $id)
    {
        // Load user data
        if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
            return Response::notFound();
        }

        // Return view
        return Response::view('edit_user.php', ['user' => $user]);
    }

    public function update(UserRepositoryInterface $repository)
    {
        // Load user model
        if (!$user = $repository->find($id)) {
            return Response::notFound();
        }

        // Update the user
        $user->first_name = $_POST['first_name'];
        $user->last_name = $_POST['last_name'];
        $user->gender = $_POST['gender'];
        $user->email = $_POST['email'];

        // Save the user
        $repository->save($user);

        // Return success
        return true;
    }

    public function delete(UserRepositoryInterface $repository)
    {
        // Load user model
        if (!$user = $repository->find($id)) {
            return Response::notFound();
        }

        // Delete the user
        $repository->delete($user);

        // Return success
        return true;
    }
}

अंतिम विचार:

यहां ध्यान देने योग्य महत्वपूर्ण बातें यह हैं कि जब मैं संस्थाओं को संशोधित (बनाना, अपडेट करना या हटाना) कर रहा हूं, तो मैं वास्तविक मॉडल ऑब्जेक्ट्स के साथ काम कर रहा हूं, और अपने रिपॉजिटरी के माध्यम से दृढ़ता का प्रदर्शन कर रहा हूं।

हालांकि, जब मैं प्रदर्शित कर रहा हूं (डेटा का चयन करना और उसे विचारों को भेजना) मैं मॉडल ऑब्जेक्ट्स के साथ काम नहीं कर रहा हूं, बल्कि सादे पुराने मूल्य की वस्तुओं के साथ काम कर रहा हूं। मैं केवल उन क्षेत्रों का चयन करता हूं जिनकी मुझे आवश्यकता है, और इसे डिज़ाइन किया गया है ताकि मैं अपने डेटा लुकअप प्रदर्शन को अधिकतम कर सकूं।

मेरे रिपॉजिटरी बहुत साफ रहते हैं, और इसके बजाय यह "गड़बड़" मेरे मॉडल प्रश्नों में व्यवस्थित है।

मैं विकास के साथ मदद करने के लिए एक डेटा मैपर का उपयोग करता हूं, क्योंकि यह सामान्य कार्यों के लिए दोहराए जाने वाले एसक्यूएल लिखने के लिए केवल हास्यास्पद है। हालाँकि, आप बिल्कुल SQL को जहाँ आवश्यक हो (जटिल प्रश्न, रिपोर्टिंग, आदि) लिख सकते हैं। और जब आप ऐसा करते हैं, तो यह अच्छी तरह से नामित कक्षा में भाग जाता है।

मैं अपने दृष्टिकोण पर अपने सुनने के लिए प्यार होता!


जुलाई 2015 अपडेट:

मुझे उन टिप्पणियों में पूछा गया है जहां मैं यह सब खत्म कर रहा हूं। ठीक है, वास्तव में दूर नहीं है। सच में, मुझे अभी भी रिपॉजिटरी पसंद नहीं है। मैं उन्हें मूल लुकअप (विशेषकर यदि आप पहले से ORM का उपयोग कर रहे हैं) के लिए ओवरकिल कर पाते हैं, और अधिक जटिल प्रश्नों के साथ काम करते समय गड़बड़ करते हैं।

मैं आम तौर पर एक ActiveRecord शैली ORM के साथ काम करता हूं, इसलिए सबसे अधिक बार मैं सिर्फ अपने आवेदन के दौरान सीधे उन मॉडलों का संदर्भ लूंगा। हालाँकि, ऐसी स्थितियों में जहाँ मेरे पास अधिक जटिल प्रश्न हैं, मैं इन अधिक पुन: प्रयोज्य बनाने के लिए क्वेरी ऑब्जेक्ट का उपयोग करूँगा। मुझे यह भी ध्यान देना चाहिए कि मैं हमेशा अपने मॉडल को अपने तरीकों में इंजेक्ट करता हूं, जिससे उन्हें मेरे परीक्षणों में मजाक करना आसान हो जाता है।


4
@PeeHaa फिर, यह उदाहरणों को सरल रखने के लिए था। यदि वे विशेष रूप से हाथ पर विषय से संबंधित नहीं हैं तो कोड के टुकड़ों को एक उदाहरण से बाहर छोड़ना बहुत आम है। हकीकत में, मैं अपनी निर्भरता में गुजर जाएगा।
योनातन

4
दिलचस्प है कि आप अपने रीड से अपने क्रिएट, अपडेट और डिलीट को विभाजित करते हैं। सोचा यह कमांड क्वैरी रिस्पॉन्सिबिलिटी सेग्रिगेशन (CQRS) का उल्लेख करने लायक होगा जो औपचारिक रूप से ऐसा करता है। martinfowler.com/bliki/CQRS.html
एडम

2
@ जोनाथन आपको अपने ही सवाल का जवाब देने में डेढ़ साल लग गए। मैं सोच रहा था कि क्या आप अभी भी आपके उत्तर से खुश हैं और यदि आपकी अधिकांश परियोजनाओं के लिए यह आपका मुख्य समाधान है? पिछले कुछ हफ्तों से मैं रिपॉजिटरी पर एलॉट पढ़ रहा हूं और मैंने देखा है कि लोगों के एलॉट की अपनी व्याख्या है कि इसे कैसे लागू किया जाना चाहिए। आपका कॉलिंग इसे ऑब्जेक्ट्स क्वेरी करता है, लेकिन यह मौजूदा पैटर्न सही है? मुझे लगता है कि मैंने इसे अन्य भाषाओं में इस्तेमाल किया है।
बोईद

1
@ जोनाथन: आप ऐसे प्रश्नों को कैसे संभालते हैं जो एक उपयोगकर्ता को "आईडी" नहीं बल्कि "उपयोगकर्ता नाम" या एक से अधिक शर्तों के साथ और भी अधिक जटिल प्रश्नों से जोड़ना चाहिए?
गिज़्मो

1
@ जिज्मो क्वेरी ऑब्जेक्ट्स का उपयोग करते हुए, आप अपने अधिक जटिल प्रश्नों के साथ मदद करने के लिए अतिरिक्त मापदंडों को पारित कर सकते हैं। उदाहरण के लिए, आप निर्माता से कर सकते हैं: new Query\ComplexUserLookup($username, $anotherCondition)। या, सेटर विधियों के माध्यम से ऐसा करें $query->setUsername($username);। आप वास्तव में इसे डिज़ाइन कर सकते हैं लेकिन यह आपके विशेष अनुप्रयोग के लिए समझ में आता है, और मुझे लगता है कि क्वेरी ऑब्जेक्ट यहां बहुत सारे लचीलेपन को छोड़ देते हैं।
जोनाथन

48

मेरे अनुभव के आधार पर, यहां आपके सवालों के जवाब दिए गए हैं:

प्रश्न: हम उन क्षेत्रों को कैसे वापस लाएंगे जिनकी हमें आवश्यकता नहीं है?

A: मेरे अनुभव से यह वास्तव में संपूर्ण संस्थाओं बनाम तदर्थ प्रश्नों से निपटने के लिए उबलता है।

एक पूर्ण इकाई एक Userवस्तु की तरह कुछ है। इसमें गुण और विधियाँ हैं, यह आपके कोडबेस में प्रथम श्रेणी का नागरिक है।

एक तदर्थ क्वेरी कुछ डेटा लौटाती है, लेकिन हम उससे आगे कुछ भी नहीं जानते हैं। जैसा कि डेटा अनुप्रयोग के चारों ओर से गुजरता है, यह बिना संदर्भ के किया जाता है। यह एक है User? एक Userकुछ के साथ Orderजानकारी संलग्न? हम वास्तव में नहीं जानते।

मैं पूरी संस्थाओं के साथ काम करना पसंद करता हूं।

आप सही हैं कि आप अक्सर उपयोग किए गए डेटा को वापस लाएंगे, लेकिन आप इसे विभिन्न तरीकों से संबोधित कर सकते हैं:

  1. आक्रामक रूप से संस्थाओं को कैश करें ताकि आप केवल डेटाबेस से एक बार पढ़ने की कीमत का भुगतान करें।
  2. अपनी संस्थाओं को मॉडलिंग करने में अधिक समय दें ताकि उनके बीच अच्छे अंतर हों। (एक बड़ी इकाई को दो छोटी संस्थाओं में विभाजित करने पर विचार करें, आदि)
  3. संस्थाओं के कई संस्करण होने पर विचार करें। आपके पास Userबैक एंड के लिए और शायद UserSmallAJAX कॉल के लिए हो सकता है । किसी में 10 गुण हो सकते हैं और किसी में 3 गुण हैं।

तदर्थ प्रश्नों के साथ काम करने की डाउनसाइड:

  1. आप अनिवार्य रूप से कई प्रश्नों के पार एक ही डेटा के साथ समाप्त होते हैं। उदाहरण के लिए, एक के साथ User, आप select *कई कॉल के लिए अनिवार्य रूप से एक ही लिखेंगे । एक कॉल में 8 में से 10 फ़ील्ड मिलेंगे, एक में 5 में से 10 मिलेंगे, एक में 7 के 10 मिलेंगे। क्यों नहीं एक को एक कॉल से बदल दिया जाए जो 10 में से 10 हो जाए? इसका कारण यह है कि यह फिर से कारक / परीक्षण / नकली करने के लिए हत्या है।
  2. समय के साथ अपने कोड के बारे में उच्च स्तर पर तर्क करना बहुत कठिन हो जाता है। " Userइतना धीमा क्यों है" जैसे बयानों के बजाय। आप एक-एक क्वेरी को ट्रैक करना समाप्त करते हैं और इसलिए बग फिक्स छोटे और स्थानीय होते हैं।
  3. अंतर्निहित तकनीक को बदलना वास्तव में कठिन है। यदि आप अभी MySQL में सब कुछ स्टोर करते हैं और MongoDB पर जाना चाहते हैं, तो 100 से अधिक तदर्थ कॉल को प्रतिस्थापित करना बहुत कठिन है, क्योंकि यह कुछ मुट्ठी भर संस्थाओं से है।

प्रश्न: मेरी रिपॉजिटरी में बहुत सारे तरीके होंगे।

एक: मैं वास्तव में कॉल को मजबूत करने के अलावा इस के आसपास कोई रास्ता नहीं देखा है। विधि आपके रिपॉजिटरी में आपके एप्लिकेशन में सुविधाओं के लिए वास्तव में मैप करती है। अधिक सुविधाएँ, अधिक डेटा विशिष्ट कॉल। आप सुविधाओं पर वापस धकेल सकते हैं और समान कॉल को एक में मर्ज करने का प्रयास कर सकते हैं।

दिन के अंत में जटिलता कहीं न कहीं मौजूद है। एक रिपॉजिटरी पैटर्न के साथ हमने इसे संग्रहित प्रक्रियाओं का एक गुच्छा बनाने के बजाय रिपॉजिटरी इंटरफ़ेस में धकेल दिया है।

कभी-कभी मुझे खुद को बताना पड़ता है, "अच्छा तो यह कहीं देना था! चांदी की गोलियां नहीं हैं।"


बहुत गहन उत्तर के लिए धन्यवाद। आप मुझे अब सोच रहे हैं। यहां मेरी बड़ी चिंता यह है कि जो कुछ भी मैं पढ़ता हूं वह नहीं कहता है SELECT *, बल्कि केवल उन क्षेत्रों का चयन करें जिनकी आपको आवश्यकता है। उदाहरण के लिए, इस प्रश्न को देखें । उन सभी तदर्थ प्रश्नों के लिए, जिनके बारे में आप बोलते हैं, मैं निश्चित रूप से समझता हूं कि आप कहां से आ रहे हैं। मेरे पास अभी बहुत बड़ा ऐप है, जिसमें से कई हैं। वह मेरा "खैर इसे कहीं देना था!" पल, मैंने अधिकतम प्रदर्शन का विकल्प चुना। हालाँकि, अब मैं विभिन्न प्रश्नों के बहुत से काम कर रहा हूँ।
जोनाथन

1
एक विचार का पालन करें। मैंने R-CUD दृष्टिकोण का उपयोग करने के लिए एक सिफारिश देखी है। चूंकि readsअक्सर ऐसी समस्याएं होती हैं, जिनमें प्रदर्शन की समस्या उत्पन्न होती है, आप उनके लिए अधिक कस्टम क्वेरी दृष्टिकोण का उपयोग कर सकते हैं, जो वास्तविक व्यावसायिक वस्तुओं में अनुवाद नहीं करते हैं। फिर, के लिए create, updateऔर delete, एक ORM का उपयोग करें, जो पूरी वस्तुओं के साथ काम करता है। उस दृष्टिकोण पर कोई विचार?
जोनाथन

1
"चयन करें" का उपयोग करने के लिए एक नोट के रूप में। मैंने इसे अतीत में किया है और यह ठीक काम किया है - जब तक कि हम varchar (अधिकतम) क्षेत्रों को नहीं मारते। उन लोगों ने हमारे प्रश्नों को मार दिया। इसलिए अगर आपके पास टाट, छोटे टेक्स्ट फील्ड आदि टेबल हैं, तो यह उतना बुरा नहीं है। अप्राकृतिक लगता है, लेकिन सॉफ्टवेयर इस तरह से जाता है। क्या बुरा था अचानक अच्छा है और इसके विपरीत।
ryan1234

1
आर-
सीयूडी

2
@ ryan1234 "दिन के अंत में जटिलता कहीं मौजूद है।" इसके लिए शुक्रिया। मुझे बेहतर महसूस कराता है।
जॉनी

20

मैं निम्नलिखित इंटरफेस का उपयोग करता हूं:

  • Repository - लोड, आवेषण, अद्यतन और संस्थाओं को हटाता है
  • Selector - फिल्टर पर आधारित संस्थाओं को एक भंडार में पाता है
  • Filter - फ़िल्टरिंग तर्क को अतिक्रमण करता है

मेरा Repositoryडेटाबेस अज्ञेयवादी है; वास्तव में यह किसी भी दृढ़ता को निर्दिष्ट नहीं करता है; यह कुछ भी हो सकता है: SQL डेटाबेस, xml फ़ाइल, दूरस्थ सेवा, बाहरी स्थान से एक विदेशी आदि। क्षमताओं की खोज के लिए, Repositoryनिर्माण Selectorजो फ़िल्टर, LIMIT-ed, सॉर्ट किए और गिने जा सकते हैं। अंत में, चयनकर्ता Entitiesदृढ़ता से एक या अधिक प्राप्त करता है।

यहाँ कुछ नमूना कोड है:

<?php
interface Repository
{
    public function addEntity(Entity $entity);

    public function updateEntity(Entity $entity);

    public function removeEntity(Entity $entity);

    /**
     * @return Entity
     */
    public function loadEntity($entityId);

    public function factoryEntitySelector():Selector
}


interface Selector extends \Countable
{
    public function count();

    /**
     * @return Entity[]
     */
    public function fetchEntities();

    /**
     * @return Entity
     */
    public function fetchEntity();
    public function limit(...$limit);
    public function filter(Filter $filter);
    public function orderBy($column, $ascending = true);
    public function removeFilter($filterName);
}

interface Filter
{
    public function getFilterName();
}

फिर, एक कार्यान्वयन:

class SqlEntityRepository
{
    ...
    public function factoryEntitySelector()
    {
        return new SqlSelector($this);
    }
    ...
}

class SqlSelector implements Selector
{
    ...
    private function adaptFilter(Filter $filter):SqlQueryFilter
    {
         return (new SqlSelectorFilterAdapter())->adaptFilter($filter);
    }
    ...
}
class SqlSelectorFilterAdapter
{
    public function adaptFilter(Filter $filter):SqlQueryFilter
    {
        $concreteClass = (new StringRebaser(
            'Filter\\', 'SqlQueryFilter\\'))
            ->rebase(get_class($filter));

        return new $concreteClass($filter);
    }
}

विचारधारा यह है कि सामान्य Selectorउपयोग करता है Filterलेकिन कार्यान्वयन SqlSelectorका उपयोग करता है SqlFilter; एक ठोस करने के SqlSelectorFilterAdapterलिए एक सामान्य adapts ।FilterSqlFilter

क्लाइंट कोड Filterऑब्जेक्ट बनाता है (जो सामान्य फ़िल्टर हैं) लेकिन चयनकर्ता के ठोस कार्यान्वयन में उन फ़िल्टर SQL फ़िल्टर में बदल जाते हैं।

अन्य चयनकर्ता कार्यान्वयन, जैसे InMemorySelector, अपने विशिष्ट का उपयोग Filterकरने से बदलना ; इसलिए, प्रत्येक चयनकर्ता कार्यान्वयन अपने स्वयं के फ़िल्टर एडाप्टर के साथ आता है।InMemoryFilterInMemorySelectorFilterAdapter

इस रणनीति का उपयोग करके मेरा क्लाइंट कोड (bussines लेयर में) एक विशिष्ट रिपॉजिटरी या चयनकर्ता कार्यान्वयन की परवाह नहीं करता है।

/** @var Repository $repository*/
$selector = $repository->factoryEntitySelector();
$selector->filter(new AttributeEquals('activated', 1))->limit(2)->orderBy('username');
$activatedUserCount = $selector->count(); // evaluates to 100, ignores the limit()
$activatedUsers = $selector->fetchEntities();

PS यह मेरे वास्तविक कोड का सरलीकरण है


"रिपॉजिटरी - संस्थाओं को लोड, आवेषण, अपडेट और डिलीट करता है" यह वह है जो "सर्विस लेयर", "डीएओ", "
बीएलएल

5

मैं इस पर थोड़ा सा जोड़ूंगा क्योंकि मैं वर्तमान में इस सब को समझने की कोशिश कर रहा हूं।

# 1 और 2

हेवी लिफ्टिंग करने के लिए यह आपके ओआरएम के लिए एक आदर्श स्थान है। यदि आप किसी ऐसे मॉडल का उपयोग कर रहे हैं जो किसी प्रकार के ओआरएम को लागू करता है, तो आप इन चीजों का ध्यान रखने के लिए इसका उपयोग कर सकते हैं। यदि आप की जरूरत है, तो अपने स्वयं के आदेश बनाएं। उदाहरण के लिए वाक्पटु का उपयोग करना:

class DbUserRepository implements UserRepositoryInterface
{
    public function findAll()
    {
        return User::all();
    }

    public function get(Array $columns)
    {
       return User::select($columns);
    }

आप जो देख रहे हैं वह एक ORM है। कोई कारण नहीं कि आपका रिपॉजिटरी एक के आसपास आधारित न हो। इसके लिए उपयोगकर्ता को वाक्पटुता की आवश्यकता होगी, लेकिन मैं व्यक्तिगत रूप से इसे समस्या के रूप में नहीं देखता।

यदि आप किसी ORM से बचना चाहते हैं, तो आपको वह "अपना रोल" देना होगा जिसे आप ढूंढ रहे हैं।

# 3

इंटरफेस कठिन और तेज आवश्यकताएं नहीं हैं। कुछ एक इंटरफ़ेस को लागू कर सकता है और इसे जोड़ सकता है। यह जो नहीं कर सकता है वह उस इंटरफ़ेस के आवश्यक फ़ंक्शन को लागू करने में विफल है। आप DRY चीजों को रखने के लिए कक्षाओं की तरह इंटरफेस भी बढ़ा सकते हैं।

उस ने कहा, मैं बस एक समझ पाने के लिए शुरू कर रहा हूं, लेकिन इन अहसासों ने मेरी मदद की है।


1
मैं इस पद्धति के बारे में नापसंद करता हूं कि यदि आपके पास एक MongoUserRepository है, तो आपकी और DbUserRepository अलग-अलग ऑब्जेक्ट लौटाएगी। Db एक एलोक्वेंट \ मॉडल, और मोंगो की अपनी कुछ वापसी कर रहा है। निश्चित रूप से एक बेहतर कार्यान्वयन दोनों रिपॉजिटरी को एक अलग एंटिटी \ उपयोगकर्ता वर्ग के इंस्टेंस / संग्रह वापस करना है। इस तरह जब आप MongoRepository
danharper

1
मैं निश्चित रूप से उस पर आपके साथ सहमत हूँ। मैं शायद इससे बचने के लिए क्या करूंगा कि एलोक्वेंट की आवश्यकता वाले वर्ग के बाहर उन तरीकों का उपयोग न करें। इसलिए, फ़ंक्शन को संभवतः निजी होना चाहिए और केवल कक्षा के भीतर ही उपयोग किया जाना चाहिए, जैसा कि आपने बताया है, कुछ ऐसा लौटाएगा जो अन्य रिपॉजिटरी नहीं कर सकते।
विल

3

मैं केवल इस बात पर टिप्पणी कर सकता हूं कि हम (मेरी कंपनी में) इससे कैसे निपटते हैं। सबसे पहले प्रदर्शन हमारे लिए बहुत अधिक मुद्दा नहीं है, लेकिन स्वच्छ / उचित कोड होना है।

सबसे पहले हम मॉडल को परिभाषित करते हैं जैसे UserModelकि UserEntityऑब्जेक्ट बनाने के लिए ORM का उपयोग करता है । जब एक UserEntityमॉडल से लोड किया जाता है तो सभी फ़ील्ड लोड होते हैं। विदेशी संस्थाओं को संदर्भित करने वाले क्षेत्रों के लिए हम संबंधित संस्थाओं को बनाने के लिए उपयुक्त विदेशी मॉडल का उपयोग करते हैं। उन संस्थाओं के लिए डेटा ऑनडैम लोड किया जाएगा। अब आपकी प्रारंभिक प्रतिक्रिया हो सकती है ... ??? ... !!! चलिए मैं आपको एक उदाहरण देता हूँ:

class UserEntity extends PersistentEntity
{
    public function getOrders()
    {
        $this->getField('orders'); //OrderModel creates OrderEntities with only the ID's set
    }
}

class UserModel {
    protected $orm;

    public function findUsers(IGetOptions $options = null)
    {
        return $orm->getAllEntities(/*...*/); // Orm creates a list of UserEntities
    }
}

class OrderEntity extends PersistentEntity {} // user your imagination
class OrderModel
{
    public function findOrdersById(array $ids, IGetOptions $options = null)
    {
        //...
    }
}

हमारे मामले $dbमें एक ORM है जो संस्थाओं को लोड करने में सक्षम है। मॉडल ओआरएम को एक विशिष्ट प्रकार की संस्थाओं के एक सेट को लोड करने का निर्देश देता है। ORM में एक मानचित्रण होता है और उस इकाई के लिए सभी फ़ील्ड को इकाई में इंजेक्ट करने के लिए इसका उपयोग करता है। विदेशी क्षेत्रों के लिए हालांकि केवल उन वस्तुओं की आईडी लोड की जाती हैं। इस मामले में केवल संदर्भित आईडी के आईडी के साथ OrderModelबनाता है OrderEntity। जब इकाई PersistentEntity::getFieldद्वारा बुलाया जाता है तो OrderEntityयह निर्देश देता है कि यह सभी क्षेत्रों को आलसी लोड करने के लिए मॉडल है OrderEntityOrderEntityएक UserEntity के साथ जुड़े सभी s को एक परिणाम-सेट के रूप में माना जाता है और एक ही बार में लोड किया जाएगा।

यहां जादू यह है कि हमारा मॉडल और ओआरएम सभी डेटा को संस्थाओं में इंजेक्ट करते हैं और यह संस्थाएं केवल getFieldआपूर्ति की गई जेनेरिक विधि के लिए आवरण कार्य प्रदान करती हैं PersistentEntity। संक्षेप में, हम हमेशा सभी क्षेत्रों को लोड करते हैं, लेकिन आवश्यक होने पर विदेशी इकाई का संदर्भ देने वाले फ़ील्ड लोड किए जाते हैं। बस खेतों का एक गुच्छा लोड करना वास्तव में एक प्रदर्शन मुद्दा नहीं है। हालांकि सभी संभावित विदेशी संस्थाओं को लोड करना एक बड़ा प्रदर्शन में कमी होगी।

अब एक क्लॉज के आधार पर, उपयोगकर्ताओं के एक विशिष्ट सेट को लोड करने के लिए। हम कक्षाओं का एक वस्तु उन्मुख पैकेज प्रदान करते हैं जो आपको सरल अभिव्यक्ति को निर्दिष्ट करने की अनुमति देता है जिसे एक साथ चिपकाया जा सकता है। उदाहरण कोड में मैंने इसे नाम दिया GetOptions। यह एक चयनित क्वेरी के लिए सभी संभावित विकल्पों के लिए एक आवरण है। इसमें क्लॉज़ का एक संग्रह होता है, जिसमें एक समूह क्लॉज़ और बाकी सब कुछ होता है। जहां क्लॉस काफी जटिल हैं, लेकिन आप स्पष्ट रूप से एक सरल संस्करण आसानी से बना सकते हैं।

$objOptions->getConditionHolder()->addConditionBind(
    new ConditionBind(
        new Condition('orderProduct.product', ICondition::OPERATOR_IS, $argObjProduct)
    )
);

इस प्रणाली का सबसे सरल संस्करण क्वेरी के WHERE भाग को सीधे मॉडल में स्ट्रिंग के रूप में पारित करना होगा।

मुझे इसके लिए काफी जटिल प्रतिक्रिया के लिए खेद है। मैंने अपने ढांचे को जल्द से जल्द और स्पष्ट करने की कोशिश की। यदि आपके पास कोई अतिरिक्त प्रश्न हैं तो उन्हें पूछने के लिए स्वतंत्र महसूस करें और मैं अपना उत्तर अपडेट करूंगा।

संपादित करें: इसके अलावा अगर आप वास्तव में कुछ क्षेत्रों को तुरंत लोड नहीं करना चाहते हैं तो आप अपने ORM मानचित्रण में एक आलसी लोडिंग विकल्प को निर्दिष्ट कर सकते हैं। क्योंकि सभी क्षेत्रों को अंततः उस getFieldविधि के माध्यम से लोड किया जाता है जब आप उस क्षेत्र को अंतिम मिनट में कुछ फ़ील्ड लोड कर सकते हैं। यह PHP में एक बहुत बड़ी समस्या नहीं है, लेकिन मैं अन्य प्रणालियों के लिए सिफारिश नहीं करूंगा।


3

ये कुछ अलग उपाय हैं जो मैंने देखे हैं। उनमें से प्रत्येक के लिए पेशेवरों और विपक्ष हैं, लेकिन यह आपके लिए तय करना है।

अंक # 1: बहुत सारे क्षेत्र

यह विशेष रूप से एक महत्वपूर्ण पहलू है जब आप इंडेक्स-ओनली स्कैन को ध्यान में रखते हैं । मैं इस समस्या से निपटने के लिए दो समाधान देखता हूं। आप एक वैकल्पिक सरणी पैरामीटर में लेने के लिए अपने कार्यों को अपडेट कर सकते हैं जिसमें वापस जाने के लिए एक कॉलम की सूची होगी। यदि यह पैरामीटर खाली है तो आप क्वेरी में सभी कॉलम वापस कर देंगे। यह थोड़ा अजीब हो सकता है; पैरामीटर के आधार पर आप एक वस्तु या एक सरणी प्राप्त कर सकते हैं। आप अपने सभी कार्यों को डुप्लिकेट भी कर सकते हैं ताकि आपके पास दो अलग-अलग फ़ंक्शन हों जो एक ही क्वेरी चलाते हैं, लेकिन एक कॉलम की एक सरणी देता है और दूसरा एक ऑब्जेक्ट देता है।

public function findColumnsById($id, array $columns = array()){
    if (empty($columns)) {
        // use *
    }
}

public function findById($id) {
    $data = $this->findColumnsById($id);
}

अंक # 2: बहुत सारी विधियाँ

मैंने एक साल पहले प्रोपेल ओआरएम के साथ संक्षेप में काम किया था और यह उस अनुभव से याद किया जा सकता है जो मुझे याद है। प्रोपेल के पास मौजूदा डेटाबेस स्कीमा के आधार पर अपनी वर्ग संरचना उत्पन्न करने का विकल्प है। यह प्रत्येक टेबल के लिए दो ऑब्जेक्ट बनाता है। पहली वस्तु आपके द्वारा वर्तमान में सूचीबद्ध के समान पहुंच फ़ंक्शन की एक लंबी सूची है; findByAttribute($attribute_value)। अगली वस्तु इस पहली वस्तु से विरासत में मिली है। आप अपने अधिक जटिल गटर कार्यों में निर्माण के लिए इस बाल वस्तु को अपडेट कर सकते हैं।

एक और समाधान __call()गैर-परिभाषित कार्यों को कुछ कार्रवाई करने के लिए मैप करने के लिए उपयोग किया जाएगा । आपकी __callविधि findById को पार्स करने में सक्षम होगी और खोज को अलग-अलग प्रश्नों में खोजेगी।

public function __call($function, $arguments) {
    if (strpos($function, 'findBy') === 0) {
        $parameter = substr($function, 6, strlen($function));
        // SELECT * FROM $this->table_name WHERE $parameter = $arguments[0]
    }
}

मुझे आशा है कि यह कम से कम कुछ मदद करता है।



0

मैं @ ryan1234 से सहमत हूं कि आपको कोड के भीतर पूरी वस्तुओं को पास करना चाहिए और उन वस्तुओं को प्राप्त करने के लिए सामान्य क्वेरी विधियों का उपयोग करना चाहिए।

Model::where(['attr1' => 'val1'])->get();

बाहरी / समापन बिंदु के उपयोग के लिए मुझे वास्तव में ग्राफकॉल विधि पसंद है।

POST /api/graphql
{
    query: {
        Model(attr1: 'val1') {
            attr2
            attr3
        }
    }
}

0

अंक # 3: इंटरफ़ेस से मिलान करना असंभव है

मैं रिपॉजिटरी के लिए इंटरफेस का उपयोग करने में लाभ देखता हूं, इसलिए मैं अपने कार्यान्वयन (परीक्षण उद्देश्यों या अन्य के लिए) को स्वैप कर सकता हूं। इंटरफेस की मेरी समझ यह है कि वे एक अनुबंध को परिभाषित करते हैं जिसे एक कार्यान्वयन का पालन करना चाहिए। यह तब तक बहुत अच्छा है जब तक कि आप अपने रिपॉजिटरी जैसे findAllInCountry () में अतिरिक्त तरीकों को जोड़ना शुरू नहीं करते हैं। अब मुझे अपने इंटरफ़ेस को अपडेट करने की भी आवश्यकता है ताकि यह विधि हो, अन्यथा, अन्य कार्यान्वयन में यह नहीं हो सकता है, और इससे मेरा आवेदन टूट सकता है। इससे पागल लगता है ... पूंछ का एक मामला कुत्ते को घेरने का।

मेरा पेट मुझे बताता है कि शायद इसके लिए एक इंटरफ़ेस की आवश्यकता होती है जो सामान्य तरीकों के साथ-साथ क्वेरी को अनुकूलित तरीकों को लागू करता है। प्रदर्शन संवेदनशील प्रश्नों में लक्षित विधियां होनी चाहिए, जबकि असंगत या हल्के-फुल्के प्रश्नों को एक सामान्य हैंडलर द्वारा नियंत्रित किया जाता है, हो सकता है कि नियंत्रक का खर्च थोड़ा अधिक कर रहा हो।

सामान्य तरीके किसी भी क्वेरी को लागू करने की अनुमति देते हैं, और इसलिए संक्रमण अवधि के दौरान परिवर्तन को रोकने से रोकते हैं। लक्षित विधियाँ आपको कॉल का अनुकूलन करने की अनुमति देती हैं जब यह समझ में आता है, और इसे कई सेवा प्रदाताओं के लिए लागू किया जा सकता है।

यह दृष्टिकोण विशिष्ट कार्यान्वित कार्य करने वाले हार्डवेयर कार्यान्वयनों के समान होगा, जबकि सॉफ़्टवेयर कार्यान्वयन प्रकाश कार्य या लचीला कार्यान्वयन करते हैं।


0

मुझे लगता है कि ग्राफकॉल डेटा रिपॉजिटरी की जटिलता को बढ़ाए बिना बड़े पैमाने पर क्वेरी भाषा प्रदान करने के लिए इस तरह के मामले में एक अच्छा उम्मीदवार है।

हालाँकि, एक और उपाय है यदि आप अभी के लिए ग्राफकॉल के लिए नहीं जाना चाहते हैं। एक डीटीओ का उपयोग करके जहां एक वस्तु का उपयोग प्रक्रियाओं के बीच डेटा को सेवा के लिए किया जाता है, इस मामले में सेवा / नियंत्रक और रिपॉजिटरी के बीच।

एक सुंदर जवाब पहले से ही ऊपर दिया गया है, हालांकि मैं एक और उदाहरण देने की कोशिश करूंगा कि मुझे लगता है कि यह सरल है और एक नई परियोजना के लिए शुरुआती बिंदु के रूप में काम कर सकता है।

जैसा कि कोड में दिखाया गया है, हमें CRUD संचालन के लिए केवल 4 विधियों की आवश्यकता होगी। findविधि लिस्टिंग और वस्तु तर्क पारित करके पढ़ने के लिए इस्तेमाल किया जाएगा। बैकएंड सेवाएं परिभाषित क्वेरी ऑब्जेक्ट को URL क्वेरी स्ट्रिंग या विशिष्ट मापदंडों के आधार पर बना सकती हैं।

SomeQueryDtoयदि आवश्यक हो तो क्वेरी ऑब्जेक्ट ( ) विशिष्ट इंटरफ़ेस भी लागू कर सकता है। और जटिलता को जोड़े बिना बाद में बढ़ाया जाना आसान है।

<?php

interface SomeRepositoryInterface
{
    public function create(SomeEnitityInterface $entityData): SomeEnitityInterface;
    public function update(SomeEnitityInterface $entityData): SomeEnitityInterface;
    public function delete(int $id): void;

    public function find(SomeEnitityQueryInterface $query): array;
}

class SomeRepository implements SomeRepositoryInterface
{
    public function find(SomeQueryDto $query): array
    {
        $qb = $this->getQueryBuilder();

        foreach ($query->getSearchParameters() as $attribute) {
            $qb->where($attribute['field'], $attribute['operator'], $attribute['value']);
        }

        return $qb->get();
    }
}

/**
 * Provide query data to search for tickets.
 *
 * @method SomeQueryDto userId(int $id, string $operator = null)
 * @method SomeQueryDto categoryId(int $id, string $operator = null)
 * @method SomeQueryDto completedAt(string $date, string $operator = null)
 */
class SomeQueryDto
{
    /** @var array  */
    const QUERYABLE_FIELDS = [
        'id',
        'subject',
        'user_id',
        'category_id',
        'created_at',
    ];

    /** @var array  */
    const STRING_DB_OPERATORS = [
        'eq' => '=', // Equal to
        'gt' => '>', // Greater than
        'lt' => '<', // Less than
        'gte' => '>=', // Greater than or equal to
        'lte' => '<=', // Less than or equal to
        'ne' => '<>', // Not equal to
        'like' => 'like', // Search similar text
        'in' => 'in', // one of range of values
    ];

    /**
     * @var array
     */
    private $searchParameters = [];

    const DEFAULT_OPERATOR = 'eq';

    /**
     * Build this query object out of query string.
     * ex: id=gt:10&id=lte:20&category_id=in:1,2,3
     */
    public static function buildFromString(string $queryString): SomeQueryDto
    {
        $query = new self();
        parse_str($queryString, $queryFields);

        foreach ($queryFields as $field => $operatorAndValue) {
            [$operator, $value] = explode(':', $operatorAndValue);
            $query->addParameter($field, $operator, $value);
        }

        return $query;
    }

    public function addParameter(string $field, string $operator, $value): SomeQueryDto
    {
        if (!in_array($field, self::QUERYABLE_FIELDS)) {
            throw new \Exception("$field is invalid query field.");
        }
        if (!array_key_exists($operator, self::STRING_DB_OPERATORS)) {
            throw new \Exception("$operator is invalid query operator.");
        }
        if (!is_scalar($value)) {
            throw new \Exception("$value is invalid query value.");
        }

        array_push(
            $this->searchParameters,
            [
                'field' => $field,
                'operator' => self::STRING_DB_OPERATORS[$operator],
                'value' => $value
            ]
        );

        return $this;
    }

    public function __call($name, $arguments)
    {
        // camelCase to snake_case
        $field = strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $name));

        if (in_array($field, self::QUERYABLE_FIELDS)) {
            return $this->addParameter($field, $arguments[1] ?? self::DEFAULT_OPERATOR, $arguments[0]);
        }
    }

    public function getSearchParameters()
    {
        return $this->searchParameters;
    }
}

उदाहरण का उपयोग:

$query = new SomeEnitityQuery();
$query->userId(1)->categoryId(2, 'ne')->createdAt('2020-03-03', 'lte');
$entities = $someRepository->find($query);

// Or by passing the HTTP query string
$query = SomeEnitityQuery::buildFromString('created_at=gte:2020-01-01&category_id=in:1,2,3');
$entities = $someRepository->find($query);
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.