โมเดลควรมีโครงสร้างอย่างไรใน MVC [ปิด]


551

ฉันเพิ่งเข้าใจเกี่ยวกับกรอบ MVC และฉันมักจะสงสัยว่าควรมีโค้ดจำนวนเท่าใดในโมเดล ฉันมักจะมี data access class ที่มีวิธีดังนี้:

public function CheckUsername($connection, $username)
{
    try
    {
        $data = array();
        $data['Username'] = $username;

        //// SQL
        $sql = "SELECT Username FROM" . $this->usersTableName . " WHERE Username = :Username";

        //// Execute statement
        return $this->ExecuteObject($connection, $sql, $data);
    }
    catch(Exception $e)
    {
        throw $e;
    }
}

โมเดลของฉันมักจะเป็นคลาสเอนทิตีที่แมปกับตารางฐานข้อมูล

วัตถุแบบจำลองควรมีคุณสมบัติที่แมปฐานข้อมูลทั้งหมดรวมถึงรหัสด้านบนหรือไม่ก็เป็นการดีที่จะแยกรหัสนั้นออกจากฐานข้อมูลที่ใช้งานได้จริงหรือไม่

ฉันจะจบลงด้วยการมีสี่ชั้นหรือไม่?


133
ทำไมคุณถึงจับยกเว้นเพื่อโยนมันอีกครั้ง?
Bailey Parker

9
@Elias Van Ootegem: คุณพลาดประเด็นไป มันไม่มีประโยชน์ที่จะจับพวกมันในกรณีนี้
Karoly Horvath

4
@Elias Van Ootegem: เหรอ? ถ้ามันทำงานได้กับ rethrow ก็หมายความว่าชั้นบนจับข้อยกเว้น แต่ถ้ามีอย่างใดอย่างหนึ่งก็จะจับได้โดยไม่ต้อง rethrow ว่า ... (ถ้าคุณยังไม่ได้รับโปรดจำลองรหัสทดสอบเล็ก ๆ )
Karoly Horvath

3
@Elias Van Ootegem: ฉันไม่รู้ว่าคุณกำลังพูดถึงเรื่องอะไรอยู่การจัดการกับข้อยกเว้นของเลเยอร์เฉพาะไม่ได้หมายความว่ามันจะหยุดแอพ โปรดสร้าง (หรือแม่นยำมากขึ้น: ไม่สามารถสร้าง) ตัวอย่างโค้ดที่จำเป็นต้องสร้างใหม่ ขอหยุดการสนทนานี้ offtopic, โปรด
Karoly Horvath

6
@drrcknlsn: นั่นเป็นอาร์กิวเมนต์ที่ถูกต้อง แต่ในกรณีนั้นอย่างน้อยก็จับข้อยกเว้นที่คุณคาดว่าจะถูกโยนไปทั่วไปExceptionไม่ได้มีค่าเอกสารมากนัก โดยส่วนตัวถ้าฉันลงไปบนถนนฉันจะเลือก PHPDoc @exceptionหรือกลไกที่คล้ายกันดังนั้นจึงปรากฏในเอกสารที่สร้างขึ้น
Karoly Horvath

คำตอบ:


903

ข้อจำกัดความรับผิดชอบ:ต่อไปนี้เป็นคำอธิบายว่าฉันเข้าใจรูปแบบ MVC ในบริบทของเว็บแอปพลิเคชันบน PHP ได้อย่างไร ลิงก์ภายนอกทั้งหมดที่ใช้ในเนื้อหาอยู่ที่นั่นเพื่ออธิบายคำศัพท์และแนวคิดและไม่แสดงถึงความน่าเชื่อถือของฉันในเรื่องนี้

สิ่งแรกที่ผมต้องชัดเจนขึ้นคือรูปแบบเป็นชั้น

ประการที่สอง: มีความแตกต่างระหว่างMVC แบบดั้งเดิมและสิ่งที่เราใช้ในการพัฒนาเว็บ ต่อไปนี้เป็นคำตอบที่เก่ากว่าที่ฉันเขียนซึ่งอธิบายสั้น ๆ ว่ามันแตกต่างกันอย่างไร

แบบจำลองไม่ใช่:

โมเดลไม่ใช่คลาสหรืออ็อบเจ็กต์เดี่ยวใด ๆ มันเป็นความผิดพลาดที่เกิดขึ้นบ่อยมาก(ฉันก็ทำเหมือนกันแม้ว่าคำตอบดั้งเดิมจะถูกเขียนเมื่อฉันเริ่มเรียนรู้อย่างอื่น)เพราะกรอบงานส่วนใหญ่จะทำให้ความเข้าใจผิดนี้ผิดไป

ไม่ว่าจะเป็นเทคนิคการทำแผนที่วัตถุเชิงสัมพันธ์ (ORM) หรือนามธรรมของตารางฐานข้อมูล ใครก็ตามที่บอกคุณเป็นอย่างอื่นน่าจะพยายาม'ขาย' ORM ใหม่ล่าสุดหรือกรอบทั้งหมด

แบบจำลองคืออะไร:

ในการปรับ MVC ที่เหมาะสม M จะมีตรรกะทางธุรกิจโดเมนทั้งหมดและModel Layerนั้นส่วนใหญ่ทำจากโครงสร้างสามประเภท:

  • วัตถุโดเมน

    วัตถุโดเมนเป็นภาชนะตรรกะของข้อมูลโดเมนล้วนๆ มันมักจะแสดงถึงหน่วยงานตรรกะในพื้นที่โดเมนปัญหา ปกติจะเรียกว่าตรรกะทางธุรกิจ

    นี่คือที่ที่คุณกำหนดวิธีการตรวจสอบข้อมูลก่อนส่งใบแจ้งหนี้หรือคำนวณต้นทุนรวมของการสั่งซื้อ ในเวลาเดียวกัน, วัตถุโดเมนจะสมบูรณ์ไม่ได้ตระหนักถึงการจัดเก็บ - ค่าจากที่ (ฐานข้อมูล SQL, REST API ไฟล์ข้อความ ฯลฯ ) หรือแม้กระทั่งถ้าพวกเขาได้รับการบันทึกไว้หรือดึง

  • Data Mappers

    วัตถุเหล่านี้รับผิดชอบการจัดเก็บเท่านั้น หากคุณเก็บข้อมูลไว้ในฐานข้อมูลนี่จะเป็นที่ที่ SQL อยู่ หรือบางทีคุณอาจใช้ไฟล์ XML เพื่อเก็บข้อมูลและตัวแมปข้อมูลของคุณแยกวิเคราะห์จากและไปยังไฟล์ XML

  • บริการ

    คุณสามารถคิดว่าพวกเขาเป็น "วัตถุระดับที่สูงขึ้นโดเมน" แต่แทนที่จะตรรกะทางธุรกิจบริการมีความรับผิดชอบในการทำงานร่วมกันระหว่างวัตถุโดเมนและMappers โครงสร้างเหล่านี้จบลงด้วยการสร้างส่วนติดต่อ "สาธารณะ" สำหรับการโต้ตอบกับตรรกะทางธุรกิจของโดเมน คุณสามารถหลีกเลี่ยงพวกเขา แต่ในบทลงโทษของการรั่วไหลตรรกะบางโดเมนเข้ามาควบคุม

    มีคำตอบที่เกี่ยวข้องกับเรื่องนี้ในคำถามการใช้งาน ACL - อาจเป็นประโยชน์

การสื่อสารระหว่างเลเยอร์โมเดลและส่วนอื่น ๆ ของ MVC สามควรเกิดขึ้นผ่านบริการเท่านั้น การแยกที่ชัดเจนมีประโยชน์เพิ่มเติมบางประการ:

  • ช่วยในการบังคับใช้หลักการความรับผิดชอบเดียว (SRP)
  • ให้เพิ่มเติม 'ห้องเลื้อย' ในกรณีที่มีการเปลี่ยนแปลงตรรกะ
  • ทำให้คอนโทรลเลอร์นั้นง่ายที่สุดเท่าที่จะทำได้
  • ให้พิมพ์เขียวที่ชัดเจนหากคุณต้องการ API ภายนอก

 

วิธีการโต้ตอบกับแบบจำลองหรือไม่?

สิ่งที่ต้องมีก่อน:ดูการบรรยาย"รัฐทั่วโลกและซิงเกิลตัน"และ"อย่ามองหาสิ่งต่าง ๆ !" จาก Clean Code Talks

รับสิทธิ์เข้าถึงอินสแตนซ์ของบริการ

สำหรับทั้งอินสแตนซ์มุมมองและตัวควบคุม (สิ่งที่คุณสามารถโทรได้: "เลเยอร์ UI") เพื่อเข้าถึงบริการเหล่านี้มีวิธีการทั่วไปสองวิธี:

  1. คุณสามารถฉีดบริการที่จำเป็นใน Constructor ของมุมมองและตัวควบคุมของคุณโดยตรงโดยเฉพาะควรใช้ DI container
  2. การใช้โรงงานสำหรับบริการเป็นการพึ่งพาที่จำเป็นสำหรับมุมมองและตัวควบคุมทั้งหมดของคุณ

ในขณะที่คุณอาจสงสัยว่าภาชนะ DI เป็นทางออกที่สวยงามกว่ามาก ทั้งสองห้องสมุดที่ผมขอแนะนำให้พิจารณาสำหรับการทำงานนี้จะเป็นแบบสแตนด์อโลน Syfmony ขององค์ประกอบ DependencyInjectionหรือAuryn

ทั้งโซลูชันที่ใช้จากโรงงานและคอนเทนเนอร์ DI จะช่วยให้คุณสามารถแบ่งปันอินสแตนซ์ของเซิร์ฟเวอร์ต่าง ๆ เพื่อใช้ร่วมกันระหว่างตัวควบคุมที่เลือกและมุมมองสำหรับวงจรการตอบสนองคำขอที่กำหนด

การเปลี่ยนแปลงสถานะของแบบจำลอง

ตอนนี้คุณสามารถเข้าถึงเลเยอร์โมเดลในคอนโทรลเลอร์ได้แล้วคุณต้องเริ่มใช้พวกมันจริง:

public function postLogin(Request $request)
{
    $email = $request->get('email');
    $identity = $this->identification->findIdentityByEmailAddress($email);
    $this->identification->loginWithPassword(
        $identity,
        $request->get('password')
    );
}

ตัวควบคุมของคุณมีงานที่ชัดเจนมาก: รับอินพุตของผู้ใช้และเปลี่ยนสถานะปัจจุบันของตรรกะทางธุรกิจ ในตัวอย่างนี้สถานะที่เปลี่ยนแปลงระหว่างนั้นคือ "ผู้ใช้ที่ไม่ระบุชื่อ" และ "ผู้ใช้ที่ล็อกอิน"

คอนโทรลเลอร์ไม่รับผิดชอบในการตรวจสอบอินพุตของผู้ใช้เนื่องจากเป็นส่วนหนึ่งของกฎเกณฑ์ทางธุรกิจและคอนโทรลเลอร์ไม่ได้เรียกคิวรี่ SQL เช่นเดียวกับที่คุณเห็นที่นี่หรือที่นี่ (โปรดอย่าเกลียดพวกเขาพวกเขาเข้าใจผิดไม่ใช่ปีศาจ)

แสดงผู้ใช้ถึงการเปลี่ยนแปลงสถานะ

ตกลงผู้ใช้เข้าสู่ระบบ (หรือล้มเหลว) ตอนนี้คืออะไร ผู้ใช้ที่กล่าวยังคงไม่รู้ตัว ดังนั้นคุณต้องสร้างการตอบสนองและนั่นคือความรับผิดชอบของมุมมอง

public function postLogin()
{
    $path = '/login';
    if ($this->identification->isUserLoggedIn()) {
        $path = '/dashboard';
    }
    return new RedirectResponse($path); 
}

ในกรณีนี้มุมมองสร้างหนึ่งในสองคำตอบที่เป็นไปได้โดยขึ้นอยู่กับสถานะปัจจุบันของเลเยอร์โมเดล สำหรับกรณีการใช้งานที่แตกต่างกันคุณจะมีมุมมองในการเลือกเทมเพลตที่แตกต่างเพื่อแสดงผลโดยอ้างอิงจาก "บทความที่เลือกไว้ในปัจจุบัน"

ชั้นนำเสนอจะได้รับจริงค่อนข้างซับซ้อนตามที่อธิบายไว้ที่นี่: เข้าใจ MVC มุมมองใน PHP

แต่ฉันเพิ่งจะสร้าง REST API!

แน่นอนว่ามีสถานการณ์เมื่อนี่คือ overkill

MVC เป็นเพียงการแก้ปัญหาที่เป็นรูปธรรมสำหรับการแยกความกังวลเกี่ยวกับหลักการ MVC แยกส่วนติดต่อผู้ใช้ออกจากตรรกะทางธุรกิจและใน UI จะแยกการจัดการอินพุตของผู้ใช้และการนำเสนอ นี่เป็นสิ่งสำคัญ ในขณะที่คนมักจะอธิบายว่ามันเป็น "สาม" มันไม่ได้สร้างขึ้นจากสามส่วนอิสระ โครงสร้างเป็นดังนี้:

การแยก MVC

หมายความว่าเมื่อตรรกะของเลเยอร์การนำเสนอของคุณใกล้เคียงกับที่ไม่มีอยู่จริงแนวทางปฏิบัติก็คือการทำให้พวกมันเป็นเลเยอร์เดียว นอกจากนี้ยังสามารถทำให้บางแง่มุมของเลเยอร์โมเดลง่ายขึ้นอย่างมาก

การใช้วิธีการนี้ตัวอย่างการเข้าสู่ระบบ (สำหรับ API) สามารถเขียนเป็น:

public function postLogin(Request $request)
{
    $email = $request->get('email');
    $data = [
        'status' => 'ok',
    ];
    try {
        $identity = $this->identification->findIdentityByEmailAddress($email);
        $token = $this->identification->loginWithPassword(
            $identity,
            $request->get('password')
        );
    } catch (FailedIdentification $exception) {
        $data = [
            'status' => 'error',
            'message' => 'Login failed!',
        ]
    }

    return new JsonResponse($data);
}

แม้ว่าสิ่งนี้จะไม่ยั่งยืนเมื่อคุณมีตรรกะที่ซับซ้อนสำหรับการสร้างเนื้อหาการตอบสนองความเรียบง่ายนี้มีประโยชน์มากสำหรับสถานการณ์ที่ไม่สำคัญ แต่ถูกเตือนวิธีนี้จะกลายเป็นฝันร้ายเมื่อพยายามที่จะใช้ในฐานรหัสขนาดใหญ่ที่มีตรรกะการนำเสนอที่ซับซ้อน

 

วิธีการสร้างแบบจำลอง?

เนื่องจากไม่มีคลาส "โมเดล" เดียว (ดังอธิบายข้างต้น) คุณไม่ได้ "สร้างโมเดล" จริงๆ แต่คุณเริ่มจากการสร้างบริการซึ่งสามารถทำวิธีการบางอย่างได้ แล้วใช้วัตถุโดเมนและMappers

ตัวอย่างของวิธีการบริการ:

ในทั้งสองวิธีข้างต้นมีวิธีการเข้าสู่ระบบนี้สำหรับบริการประจำตัวประชาชน มันจะมีลักษณะเป็นอย่างไร ฉันกำลังใช้ฟังก์ชั่นรุ่นเดียวกันที่ได้รับการแก้ไขเล็กน้อยจากห้องสมุดฉันเขียน .. เพราะฉันขี้เกียจ:

public function loginWithPassword(Identity $identity, string $password): string
{
    if ($identity->matchPassword($password) === false) {
        $this->logWrongPasswordNotice($identity, [
            'email' => $identity->getEmailAddress(),
            'key' => $password, // this is the wrong password
        ]);

        throw new PasswordMismatch;
    }

    $identity->setPassword($password);
    $this->updateIdentityOnUse($identity);
    $cookie = $this->createCookieIdentity($identity);

    $this->logger->info('login successful', [
        'input' => [
            'email' => $identity->getEmailAddress(),
        ],
        'user' => [
            'account' => $identity->getAccountId(),
            'identity' => $identity->getId(),
        ],
    ]);

    return $cookie->getToken();
}

อย่างที่คุณเห็นในระดับความเป็นนามธรรมนี้ไม่มีข้อบ่งชี้ว่าข้อมูลถูกดึงมาจากที่ใด อาจเป็นฐานข้อมูล แต่อาจเป็นเพียงวัตถุจำลองสำหรับการทดสอบ แม้แต่ตัวแมปข้อมูลที่ใช้งานจริงนั้นจะถูกซ่อนอยู่ในprivateวิธีการของบริการนี้

private function changeIdentityStatus(Entity\Identity $identity, int $status)
{
    $identity->setStatus($status);
    $identity->setLastUsed(time());
    $mapper = $this->mapperFactory->create(Mapper\Identity::class);
    $mapper->store($identity);
}

วิธีสร้าง Mappers

ที่จะใช้เป็นนามธรรมของการคงอยู่บนแนวทางความยืดหยุ่นมากที่สุดคือการสร้างที่กำหนดเองทำแผนที่ข้อมูล

แผนภาพ Mapper

จาก: หนังสือPoEAA

ในทางปฏิบัติมีการใช้งานสำหรับการโต้ตอบกับคลาสหรือซูเปอร์คลาสที่เฉพาะเจาะจง ให้บอกว่าคุณมีCustomerและAdminในรหัสของคุณ (ทั้งสืบทอดมาจากUserซูเปอร์คลาส) ทั้งสองอาจจะจบลงด้วยการจับคู่ mapper แยกเนื่องจากพวกเขามีเขตข้อมูลที่แตกต่างกัน แต่คุณจะจบลงด้วยการดำเนินการที่ใช้ร่วมกันและที่ใช้กันทั่วไป ตัวอย่างเช่น: อัปเดตเวลา"ออนไลน์ล่าสุดที่เห็น" และแทนที่จะทำให้ผู้ทำแผนที่ที่มีอยู่มีความเชื่อมั่นมากขึ้นวิธีการที่เป็นประโยชน์มากขึ้นก็คือการมี "User Mapper" ทั่วไปซึ่งอัพเดตเฉพาะการประทับเวลานั้น

ความคิดเห็นเพิ่มเติมบางส่วน:

  1. ตารางและโมเดลฐานข้อมูล

    ในขณะที่บางครั้งมีความสัมพันธ์โดยตรง 1: 1: 1 ระหว่างตารางฐานข้อมูล, Domain ObjectและMapperในโครงการขนาดใหญ่อาจมีน้อยกว่าที่คุณคาดไว้:

    • ข้อมูลที่ใช้โดยวัตถุโดเมนเดียวอาจถูกแมปจากตารางที่แตกต่างกันในขณะที่วัตถุนั้นไม่มีอยู่ในฐานข้อมูล

      ตัวอย่าง:หากคุณกำลังสร้างรายงานรายเดือน สิ่งนี้จะรวบรวมข้อมูลจากตารางที่แตกต่างกัน แต่ไม่มีMonthlyReportตารางขลังในฐานข้อมูล

    • Mapperเดียวสามารถส่งผลกระทบต่อหลายตาราง

      ตัวอย่าง:เมื่อคุณเก็บข้อมูลจากUserวัตถุวัตถุโดเมนนี้อาจมีการรวบรวมวัตถุโดเมนอื่น ๆ - Groupอินสแตนซ์ หากคุณดัดแปลงพวกเขาและเก็บUserที่Mapper ข้อมูลจะมีการปรับปรุงและ / หรือแทรกรายการในตารางหลาย

    • ข้อมูลจากวัตถุโดเมนเดียวจะถูกเก็บไว้ในมากกว่าหนึ่งตาราง

      ตัวอย่าง:ในระบบขนาดใหญ่ (คิดว่า: เครือข่ายทางสังคมขนาดกลาง) อาจเป็นวิธีที่ใช้ในการจัดเก็บข้อมูลการตรวจสอบผู้ใช้และข้อมูลที่เข้าถึงบ่อยแยกต่างหากจากเนื้อหาขนาดใหญ่ซึ่งไม่ค่อยจำเป็น ในกรณีนี้คุณยังอาจมีUserชั้นเดียวแต่ข้อมูลนั้นจะขึ้นอยู่กับว่ามีการดึงรายละเอียดทั้งหมด

    • สำหรับทุกDomain Objectจะมีผู้ทำแผนที่มากกว่าหนึ่งคน

      ตัวอย่าง:คุณมีเว็บไซต์ข่าวที่มีรหัสที่ใช้ร่วมกันสำหรับทั้งสาธารณะและซอฟต์แวร์การจัดการ แต่ในขณะที่อินเทอร์เฟซทั้งสองใช้Articleคลาสเดียวกันการจัดการต้องการข้อมูลเพิ่มเติมที่บรรจุอยู่ในนั้น ในกรณีนี้คุณจะมีผู้ทำแผนที่สองคนแยกกัน: "ภายใน" และ "ภายนอก" แต่ละแบบสอบถามที่มีประสิทธิภาพแตกต่างกันหรือแม้กระทั่งใช้ฐานข้อมูลที่แตกต่างกัน (เช่นในต้นแบบหรือทาส)

  2. มุมมองไม่ใช่แม่แบบ

    ดูอินสแตนซ์ใน MVC (หากคุณไม่ได้ใช้รูปแบบ MVP ของรูปแบบ) รับผิดชอบต่อตรรกะเชิงการนำเสนอ ซึ่งหมายความว่าแต่ละมุมมองจะเล่นปาหี่อย่างน้อยสองสามเทมเพลต ได้รับข้อมูลจากModel Layerจากนั้นตามข้อมูลที่ได้รับเลือกแม่แบบและตั้งค่า

    ประโยชน์อย่างหนึ่งที่คุณได้รับจากการใช้งานนี้คือ หากคุณสร้างListViewคลาสจากนั้นด้วยรหัสที่ดีคุณสามารถให้คลาสเดียวกันส่งงานนำเสนอรายการผู้ใช้และความคิดเห็นด้านล่างบทความ เพราะทั้งคู่มีตรรกะการนำเสนอเหมือนกัน คุณเพียงแค่สลับเทมเพลต

    คุณสามารถใช้เทมเพลต PHP ดั้งเดิมหรือใช้เครื่องมือสร้างเทมเพลตของ บริษัท อื่น นอกจากนี้ยังอาจมีห้องสมุดบุคคลที่สามซึ่งสามารถแทนที่ดูอินสแตนซ์ได้อย่างสมบูรณ์

  3. แล้วคำตอบรุ่นเก่าล่ะ?

    สิ่งเดียวที่เปลี่ยนแปลงที่สำคัญคือสิ่งที่เรียกว่ารุ่นในรุ่นเก่าเป็นจริงบริการ ส่วนที่เหลือของ "การเปรียบเทียบของห้องสมุด" นั้นค่อนข้างดี

    ข้อบกพร่องเพียงอย่างเดียวที่ฉันเห็นคือมันจะเป็นห้องสมุดที่แปลกจริง ๆ เพราะมันจะคืนข้อมูลให้คุณจากหนังสือ แต่ไม่อนุญาตให้คุณสัมผัสหนังสือเองเพราะมิฉะนั้นสิ่งที่เป็นนามธรรมจะเริ่มเป็น ฉันอาจต้องคิดถึงการเปรียบเทียบที่เหมาะสมกว่านี้

  4. ความสัมพันธ์ระหว่างอินสแตนซ์มุมมองและตัวควบคุมคืออะไร

    โครงสร้าง MVC ประกอบด้วยสองชั้น: ui และรุ่น โครงสร้างหลักในเลเยอร์ UIคือมุมมองและตัวควบคุม

    เมื่อคุณจัดการกับเว็บไซต์ที่ใช้รูปแบบการออกแบบ MVC วิธีที่ดีที่สุดคือมีความสัมพันธ์แบบ 1: 1 ระหว่างมุมมองและตัวควบคุม แต่ละมุมมองแสดงทั้งหน้าในเว็บไซต์ของคุณและมีตัวควบคุมเฉพาะเพื่อจัดการคำขอขาเข้าทั้งหมดสำหรับมุมมองนั้น

    ตัวอย่างเช่นในการเป็นตัวแทนของบทความเปิดคุณจะต้องและ\Application\Controller\Document \Application\View\Documentนี้จะมีทุกฟังก์ชั่นหลักสำหรับชั้น UI เมื่อมันมาถึงการจัดการกับบทความ(แน่นอนคุณอาจมีบางXHRส่วนประกอบที่ไม่ได้เกี่ยวข้องโดยตรงกับบทความ)


4
@ รินซ์เลอร์คุณจะสังเกตเห็นว่าไม่มีที่ไหนในลิงค์นี้มีอะไรพูดถึงโมเดล (ยกเว้นในคอมเม้นท์เดียว) มันเป็นเพียง"อินเตอร์เฟซที่เชิงวัตถุตารางฐานข้อมูล" ถ้าคุณพยายามที่จะปั้นนี้ในรุ่นเหมือนสิ่งที่คุณท้ายละเมิดSRPและLSP
tereško

8
@hafichuk เฉพาะสถานการณ์เมื่อมีความสมเหตุสมผลในการใช้รูปแบบActiveRecordสำหรับการสร้างต้นแบบ เมื่อคุณเริ่มเขียนโค้ดที่มีความหมายต่อการผลิตมันจะกลายเป็นรูปแบบการต่อต้าน และเนื่องจากModel Layerนั้นไม่รู้ส่วนอื่น ๆ ของ MVC อย่างสมบูรณ์ นี้ไม่ได้เปลี่ยนแปลงขึ้นอยู่กับรูปแบบในรูปแบบเดิม แม้เมื่อใช้ MVVM ไม่มี "หลายรุ่น" และไม่มีการแมปกับสิ่งใด แบบจำลองเป็นเลเยอร์
tereško

3
เวอร์ชั่นสั้น - รุ่นมีโครงสร้างข้อมูล
Eddie B

9
เมื่อเห็นว่าเขาคิดค้น MVC บทความนี้อาจมีข้อดี
Eddie B

3
... หรือแม้กระทั่งเพียงชุดฟังก์ชั่น MVC ไม่จำเป็นต้องนำมาใช้ในรูปแบบ OOP แม้ว่าส่วนใหญ่จะถูกนำไปใช้ในลักษณะนั้น สิ่งที่สำคัญที่สุดคือการแยกเลเยอร์และสร้างข้อมูลที่ถูกต้องและการควบคุมการไหล
hek2mgl

37

ทุกอย่างที่เป็นตรรกะทางธุรกิจอยู่ในแบบจำลองไม่ว่าจะเป็นแบบสอบถามฐานข้อมูลการคำนวณการเรียกใช้ REST เป็นต้น

คุณสามารถเข้าถึงข้อมูลในตัวแบบเองได้รูปแบบ MVC ไม่ได้ จำกัด ให้คุณทำเช่นนั้น คุณสามารถเคลือบด้วยบริการผู้ทำแผนที่และสิ่งที่ไม่ได้ แต่ความหมายที่แท้จริงของรูปแบบคือเลเยอร์ที่จัดการกับตรรกะทางธุรกิจไม่มีอะไรมากไม่มีอะไรน้อย มันอาจเป็นคลาสฟังก์ชั่นหรือโมดูลที่สมบูรณ์ด้วยวัตถุ gazillion ถ้านั่นคือสิ่งที่คุณต้องการ

มันง่ายกว่าเสมอที่จะมีวัตถุแยกต่างหากที่ประมวลผลการสืบค้นฐานข้อมูลแทนที่จะให้พวกมันถูกเรียกใช้งานในโมเดลโดยตรง: สิ่งนี้จะมีประโยชน์โดยเฉพาะอย่างยิ่งเมื่อทำการทดสอบหน่วย (เนื่องจากความง่ายในการฉีดการพึ่งพาฐานข้อมูลจำลอง

class Database {
   protected $_conn;

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

   public function ExecuteObject($sql, $data) {
       // stuff
   }
}

abstract class Model {
   protected $_db;

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

class User extends Model {
   public function CheckUsername($username) {
       // ...
       $sql = "SELECT Username FROM" . $this->usersTableName . " WHERE ...";
       return $this->_db->ExecuteObject($sql, $data);
   }
}

$db = new Database($conn);
$model = new User($db);
$model->CheckUsername('foo');

นอกจากนี้ใน PHP คุณไม่จำเป็นต้องจับ / rethrow ข้อยกเว้นเนื่องจาก backtrace ถูกเก็บรักษาไว้โดยเฉพาะในกรณีเช่นตัวอย่างของคุณ เพียงปล่อยให้ข้อยกเว้นถูกโยนและจับมันในตัวควบคุมแทน


โครงสร้างของฉันคล้ายกันมากฉันคิดว่าฉันแยกมันออกมาอีกหน่อย เหตุผลที่ฉันผ่านรอบการเชื่อมต่อเป็นเพราะฉันต้องมีการทำงานในการทำธุรกรรมชิ้น ฉันต้องการเพิ่มผู้ใช้แล้วเพิ่มผู้ใช้เข้ากับบทบาท แต่กลับบทบาทหากล้มเหลว วิธีเดียวที่ฉันจะแยกแยะได้ก็คือผ่านการเชื่อมต่อ
Dietpixel

10
-1: มันเกิดขึ้นผิดอย่างสมบูรณ์เช่นกัน Model ไม่ใช่สิ่งที่เป็นนามธรรมสำหรับตาราง
tereško

1
Userระดับพื้นขยายรุ่น แต่ itsn't วัตถุ ผู้ใช้ควรเป็นวัตถุและมีคุณสมบัติเช่น: id, ชื่อ ... คุณกำลังปรับใช้Userคลาสเป็นผู้ช่วย
TomSawyer

1
ฉันคิดว่าคุณเข้าใจ MVC แต่ไม่เข้าใจว่า OOP คืออะไร ในสถานการณ์นี้อย่างที่ฉันพูดUserย่อมาจากวัตถุและควรมีคุณสมบัติของผู้ใช้ไม่ใช่วิธีการเช่นCheckUsernameคุณควรทำอย่างไรถ้าคุณต้องการสร้างUserวัตถุใหม่ new User($db)
TomSawyer

@TomSawyer OOP ไม่ได้หมายความว่าวัตถุจะต้องมีคุณสมบัติ สิ่งที่คุณอธิบายคือรูปแบบการออกแบบซึ่งไม่เกี่ยวข้องกับคำถามหรือคำตอบสำหรับคำถามนั้น OOP เป็นรูปแบบภาษาไม่ใช่รูปแบบการออกแบบ
netcoder

20

ในเว็บ - "MVC" คุณสามารถทำอะไรก็ได้ที่คุณต้องการ

แนวคิดดั้งเดิม(1)อธิบายโมเดลว่าเป็นตรรกะทางธุรกิจ ควรแสดงสถานะแอปพลิเคชันและบังคับใช้ความสอดคล้องของข้อมูลบางอย่าง วิธีการนั้นมักถูกอธิบายว่าเป็น "รูปแบบไขมัน"

เฟรมเวิร์ค PHP ส่วนใหญ่ทำตามแนวทางที่ตื้นกว่าซึ่งโมเดลเป็นเพียงส่วนต่อประสานฐานข้อมูล แต่อย่างน้อยที่สุดโมเดลเหล่านี้ยังควรตรวจสอบความถูกต้องของข้อมูลและความสัมพันธ์ที่เข้ามา

ไม่ว่าจะด้วยวิธีใดคุณจะอยู่ไม่ไกลถ้าแยก SQL หรือการเรียกฐานข้อมูลออกเป็นอีกเลเยอร์ วิธีนี้คุณเพียงแค่ต้องกังวลกับข้อมูล / พฤติกรรมจริงไม่ใช่กับ API การจัดเก็บจริง (อย่างไรก็ตามมันไม่มีเหตุผลที่จะหักล้างมันมากเกินไปคุณจะไม่สามารถแทนที่แบ็กเอนด์ฐานข้อมูลด้วย filestorage ถ้ามันไม่ได้ถูกออกแบบมาก่อน)


8
ลิงก์ไม่ถูกต้อง (404)
Kyslik


6

เพิ่มเติม oftenly ที่สุดของการใช้งานจะมีข้อมูลการแสดงผลและการประมวลผลส่วนหนึ่งและเราก็ใส่ทุกคนที่อยู่ในตัวอักษรM, และ VC

Model ( M) -> มีแอตทริบิวต์ที่ถือสถานะของแอพลิเคชันและไม่ทราบว่าสิ่งใด ๆ เกี่ยวกับและ VC

View ( V) -> Cมีการแสดงรูปแบบสำหรับการใช้งานและและมีเพียงรู้เกี่ยวกับวิธีการย่อยรูปแบบเกี่ยวกับมันและไม่ได้กังวลเกี่ยวกับ

Controller ( C) ----> มีการประมวลผลส่วนหนึ่งของโปรแกรมและทำหน้าที่เป็นสายไฟระหว่าง M และ V และมันขึ้นอยู่กับทั้งสองM, Vแตกต่างและ MV

โดยสิ้นเชิงมีการแยกความกังวลระหว่างกัน ในอนาคตการเปลี่ยนแปลงหรือการปรับปรุงใด ๆ สามารถเพิ่มได้ง่ายมาก


0

ในกรณีของฉันฉันมีคลาสฐานข้อมูลที่จัดการการโต้ตอบฐานข้อมูลโดยตรงทั้งหมดเช่นการสืบค้นการดึงข้อมูลและอื่น ๆ ดังนั้นถ้าฉันต้องเปลี่ยนฐานข้อมูลจากMySQLเป็นPostgreSQLจะไม่มีปัญหาใด ๆ ดังนั้นการเพิ่มเลเยอร์พิเศษนั้นจะมีประโยชน์

แต่ละตารางสามารถมีคลาสของตัวเองและมีวิธีการเฉพาะ แต่เพื่อรับข้อมูลจริง ๆ จะช่วยให้คลาสฐานข้อมูลจัดการได้:

ไฟล์ Database.php

class Database {
    private static $connection;
    private static $current_query;
    ...

    public static function query($sql) {
        if (!self::$connection){
            self::open_connection();
        }
        self::$current_query = $sql;
        $result = mysql_query($sql,self::$connection);

        if (!$result){
            self::close_connection();
            // throw custom error
            // The query failed for some reason. here is query :: self::$current_query
            $error = new Error(2,"There is an Error in the query.\n<b>Query:</b>\n{$sql}\n");
            $error->handleError();
        }
        return $result;
    }
 ....

    public static function find_by_sql($sql){
        if (!is_string($sql))
            return false;

        $result_set = self::query($sql);
        $obj_arr = array();
        while ($row = self::fetch_array($result_set))
        {
            $obj_arr[] = self::instantiate($row);
        }
        return $obj_arr;
    }
}

ตารางวัตถุ classL

class DomainPeer extends Database {

    public static function getDomainInfoList() {
        $sql = 'SELECT ';
        $sql .='d.`id`,';
        $sql .='d.`name`,';
        $sql .='d.`shortName`,';
        $sql .='d.`created_at`,';
        $sql .='d.`updated_at`,';
        $sql .='count(q.id) as queries ';
        $sql .='FROM `domains` d ';
        $sql .='LEFT JOIN queries q on q.domainId = d.id ';
        $sql .='GROUP BY d.id';
        return self::find_by_sql($sql);
    }

    ....
}

ฉันหวังว่าตัวอย่างนี้ช่วยให้คุณสร้างโครงสร้างที่ดี


12
"ดังนั้นถ้าฉันต้องเปลี่ยนฐานข้อมูลจาก MySQL เป็น PostgreSQL จะไม่มีปัญหาใด ๆ " อืมมมกับโค้ดด้านบนคุณจะมีปัญหาใหญ่ในการเปลี่ยนแปลงสิ่งต่างๆ
PeeHaa

ฉันเห็นคำตอบของฉันเหมาะสมน้อยลงหลังจากการแก้ไขและเมื่อเวลาผ่านไป แต่มันควรจะอยู่ที่นี่
Ibu

2
Databaseในตัวอย่างไม่ใช่คลาส มันเป็นเพียงเสื้อคลุมสำหรับฟังก์ชั่น นอกจากนี้คุณจะมี "คลาสวัตถุตาราง" โดยไม่มีวัตถุได้อย่างไร
tereško

2
@ tereškoฉันได้อ่านโพสต์ของคุณมากมายแล้วมันเยี่ยมมาก แต่ฉันไม่สามารถหากรอบงานที่สมบูรณ์ที่ใดก็ได้เพื่อศึกษา คุณรู้หรือไม่เกี่ยวกับสิ่งที่ "ทำถูกต้อง"? หรืออย่างน้อยหนึ่งที่ไม่ชอบคุณและคนอื่น ๆ ที่นี่เพื่อพูดจะทำอย่างไร ขอบคุณ
johnny

ฉันอาจจะมาสาย แต่ฉันอยากจะชี้ให้เห็นว่า PDO เกือบจะแก้ปัญหาที่ต้องสร้าง 'เลเยอร์' DB เพื่อรองรับการเปลี่ยนแปลงในอนาคต
Matthew Goulart
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.