Trong một dự án PHP, những mẫu nào tồn tại để lưu trữ, truy cập và tổ chức các đối tượng trợ giúp? [đóng cửa]


114

Làm cách nào để bạn tổ chức và quản lý các đối tượng trợ giúp của mình như công cụ cơ sở dữ liệu, thông báo người dùng, xử lý lỗi, v.v. trong một dự án hướng đối tượng dựa trên PHP?

Giả sử tôi có một CMS PHP lớn. CMS được tổ chức theo nhiều lớp khác nhau. Một vài ví dụ:

  • đối tượng cơ sở dữ liệu
  • quản lý người dùng
  • một API để tạo / sửa đổi / xóa các mục
  • một đối tượng nhắn tin để hiển thị tin nhắn cho người dùng cuối
  • một trình xử lý ngữ cảnh đưa bạn đến đúng trang
  • một lớp thanh điều hướng hiển thị các nút
  • một đối tượng ghi nhật ký
  • có thể, xử lý lỗi tùy chỉnh

Vân vân.

Tôi đang giải quyết một câu hỏi muôn thuở, làm thế nào để làm cho các đối tượng này có thể truy cập tốt nhất đến từng phần của hệ thống cần nó.

ứng dụng đầu tiên của tôi, nhiều năm trước là có $ application global chứa các phiên bản khởi tạo của các lớp này.

global $application;
$application->messageHandler->addMessage("Item successfully inserted");

Sau đó, tôi đã thay đổi sang mẫu Singleton và một hàm nhà máy:

$mh =&factory("messageHandler");
$mh->addMessage("Item successfully inserted");

nhưng tôi cũng không hài lòng với điều đó. Các bài kiểm tra đơn vị và tính đóng gói ngày càng trở nên quan trọng hơn đối với tôi, và theo hiểu biết của tôi, logic đằng sau các khối cầu / hạt đơn đã phá hủy ý tưởng cơ bản về OOP.

Sau đó, tất nhiên có khả năng cung cấp cho mỗi đối tượng một số con trỏ đến các đối tượng trợ giúp mà nó cần, có thể là cách sạch nhất, tiết kiệm tài nguyên và thân thiện với thử nghiệm nhưng tôi nghi ngờ về khả năng duy trì của điều này trong thời gian dài.

Hầu hết các khuôn khổ PHP mà tôi đã xem xét đều sử dụng mô hình singleton hoặc các hàm truy cập các đối tượng được khởi tạo. Cả hai cách tiếp cận tốt, nhưng như tôi đã nói, tôi hài lòng với cả hai.

Tôi muốn mở rộng chân trời của mình về những mẫu phổ biến tồn tại ở đây. Tôi đang tìm kiếm các ví dụ, ý tưởng bổ sung và các gợi ý hướng tới các nguồn thảo luận về vấn đề này từ quan điểm dài hạn , trong thế giới thực .

Ngoài ra, tôi muốn nghe về cách tiếp cận chuyên biệt, thích hợp hoặc đơn giản là kỳ lạ đối với vấn đề.


1
Tôi vừa hỏi một câu hỏi cực kỳ tương tự và cũng có tiền thưởng. Bạn có thể đánh giá cao một số câu trả lời đó: stackoverflow.com/questions/1967548/...
philfreo

3
Việc trả về một đối tượng mới bằng tham chiếu - giống như $mh=&factory("messageHandler");là vô nghĩa và không mang lại bất kỳ lợi ích hiệu suất nào. Ngoài ra, điều này không được chấp nhận trong 5.3.
ryeguy

Câu trả lời:


68

Tôi sẽ tránh cách tiếp cận Singleton do Flavius ​​đề xuất. Có rất nhiều lý do để tránh cách tiếp cận này. Nó vi phạm các nguyên tắc tốt của OOP. Blog kiểm tra của google có một số bài viết hay về Singleton và cách tránh nó:

http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-join-new-project.html http://googletesting.blogspot.com/2008/05/tott-using-dependancy -injection-to.html http://googletesting.blogspot.com/2008/08/where-have-all-singletons-gone.html

Giải pháp thay thế

  1. một nhà cung cấp dịch vụ

    http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html

  2. tiêm phụ thuộc

    http://en.wikipedia.org/wiki/Dependency_injection

    và giải thích php:

    http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection

Đây là một bài viết hay về các lựa chọn thay thế này:

http://martinfowler.com/articles/injection.html

Thực hiện tiêm phụ thuộc (DI):

Một số suy nghĩ thêm về giải pháp của Flavius. Tôi không muốn bài đăng này trở thành một bài phản đối nhưng tôi nghĩ rằng điều quan trọng là phải xem tại sao việc tiêm phụ thuộc, ít nhất là đối với tôi, tốt hơn toàn cầu.

Mặc dù nó không phải là một triển khai Singleton 'đúng' , tôi vẫn nghĩ Flavius ​​đã sai. Trạng thái toàn cầu là xấu . Lưu ý rằng các giải pháp như vậy cũng sử dụng các phương pháp tĩnh khó kiểm tra .

Tôi biết rất nhiều người làm điều đó, chấp thuận và sử dụng nó. Nhưng việc đọc các bài viết trên blog của Misko Heverys ( một chuyên gia về khả năng kiểm tra của google ), đọc lại nó và từ từ tiêu hóa những gì anh ấy nói đã làm thay đổi cách tôi nhìn thiết kế rất nhiều.

Nếu bạn muốn có thể kiểm tra ứng dụng của mình, bạn cần áp dụng một cách tiếp cận khác để thiết kế ứng dụng của mình. Khi bạn thực hiện lập trình thử nghiệm đầu tiên, bạn sẽ gặp khó khăn với những thứ như sau: 'tiếp theo, tôi muốn triển khai đăng nhập đoạn mã này; trước tiên hãy viết một bài kiểm tra ghi lại một thông báo cơ bản 'và sau đó đưa ra một bài kiểm tra buộc bạn phải viết và sử dụng một trình ghi nhật ký chung không thể thay thế được.

Tôi vẫn đang đấu tranh với tất cả thông tin tôi có được từ blog đó và nó không phải lúc nào cũng dễ thực hiện và tôi có nhiều câu hỏi. Nhưng không có cách nào tôi có thể quay lại những gì tôi đã làm trước đây (vâng, trạng thái toàn cầu và Singletons (chữ S lớn)) sau khi tôi hiểu Misko Hevery đang nói gì :-)


+1 cho DI. Mặc dù tôi không sử dụng nó nhiều như tôi muốn, nhưng nó rất hữu ích với bất kỳ số lượng nhỏ nào mà tôi đã sử dụng.
Anurag

1
@koen: Bạn muốn đưa ra một ví dụ PHP về việc triển khai DI / SP trong PHP? Có thể mã @Flavius ​​được triển khai bằng cách sử dụng các mẫu thay thế mà bạn đã đề xuất?
Alix Axel

Đã thêm liên kết đến triển khai DI và vùng chứa trong câu trả lời của tôi.
Thomas

Tôi đang đọc tất cả những điều này bây giờ nhưng tôi chưa đọc hết, tôi muốn hỏi, về cơ bản một khung phụ thuộc có phải là một Registry không?
JasonDavis

Không thật sự lắm. Nhưng một vùng chứa phụ thuộc cũng có thể phục vụ như một sổ đăng ký. Chỉ cần đọc các liên kết tôi đã đăng trong câu trả lời của tôi. Các khái niệm của DI được giải thích thực sự thực tế.
Thomas

16
class Application {
    protected static $_singletonFoo=NULL;

    public static function foo() {
        if(NULL === self::$_singletonFoo) {
            self::$_singletonFoo = new Foo;
        }
        return self::$_singletonFoo;
    }

}

Đây là cách tôi sẽ làm. Nó tạo ra đối tượng theo yêu cầu:

Application::foo()->bar();

Đó là cách tôi đang làm, nó tôn trọng các nguyên tắc OOP, nó ít mã hơn so với cách bạn đang làm hiện tại và đối tượng chỉ được tạo khi mã cần nó lần đầu tiên.

Lưu ý : những gì tôi đã trình bày thậm chí không phải là một mẫu singleton thực sự. Một singleton sẽ chỉ cho phép một thể hiện của chính nó bằng cách xác định hàm tạo (Foo :: __ constructor ()) là private. Nó chỉ là một biến "toàn cục" có sẵn cho tất cả các phiên bản "Ứng dụng". Đó là lý do tại sao tôi nghĩ việc sử dụng nó là hợp lệ vì nó KHÔNG bỏ qua các nguyên tắc tốt của OOP. Tất nhiên, như bất cứ điều gì trên thế giới, "khuôn mẫu" này cũng không nên được lạm dụng!

Tôi đã thấy điều này được sử dụng trong nhiều framework PHP, Zend Framework và Yii trong số đó. Và bạn nên sử dụng một khuôn khổ. Tôi sẽ không nói cho bạn biết cái nào.

Phụ lục Đối với những người trong số bạn lo lắng về TDD , bạn vẫn có thể tạo ra một số dây để tiêm phụ thuộc vào nó. Nó có thể trông như thế này:

class Application {
        protected static $_singletonFoo=NULL;
        protected static $_helperName = 'Foo';

        public static function setDefaultHelperName($helperName='Foo') {
                if(is_string($helperName)) {
                        self::$_helperName = $helperName;
                }
                elseif(is_object($helperName)) {
                        self::$_singletonFoo = $helperName;
                }
                else {
                        return FALSE;
                }
                return TRUE;
        }
        public static function foo() {
                if(NULL === self::$_singletonFoo) {
                        self::$_singletonFoo = new self::$_helperName;
                }
                return self::$_singletonFoo;
        }
}

Có đủ chỗ để cải thiện. Nó chỉ là một PoC, hãy sử dụng trí tưởng tượng của bạn.

Tại sao nó lại như vậy? Chà, hầu hết thời gian ứng dụng sẽ không được kiểm tra đơn vị, nó sẽ thực sự được chạy, hy vọng là trong môi trường sản xuất . Điểm mạnh của PHP là tốc độ của nó. PHP KHÔNG và sẽ không bao giờ là một "ngôn ngữ OOP sạch", như Java.

Trong một ứng dụng, chỉ có một lớp Ứng dụng và tối đa chỉ có một trường hợp của mỗi lớp trợ giúp của nó (theo kiểu tải chậm như trên). Chắc chắn, những người hát rong rất tệ, nhưng một lần nữa, chỉ khi chúng không tuân theo thế giới thực. Trong ví dụ của tôi, họ làm.

Những "quy tắc" rập khuôn như "người độc thân là xấu" là nguồn gốc của cái ác, chúng dành cho những người lười biếng, không chịu nghĩ cho bản thân.

Vâng, tôi biết, tuyên ngôn PHP là XẤU, về mặt kỹ thuật. Tuy nhiên, đó là một ngôn ngữ thành công, theo cách khó hiểu của nó.

Phụ lục

Một kiểu chức năng:

function app($class) {
    static $refs = array();

    //> Dependency injection in case of unit test
    if (is_object($class)) {
        $refs[get_class($class)] = $class;
        $class = get_class($class);
    }

    if (!isset($refs[$class]))
        $refs[$class] = new $class();

    return $refs[$class];
}

//> usage: app('Logger')->doWhatever();

2
Tôi đã từ chối câu trả lời vì tôi tin rằng đề xuất mô hình singleton để xử lý vấn đề đi ngược lại các nguyên tắc OOP vững chắc.
koen

1
@koen: những gì bạn đang nói là đúng, nói chung, NHƯNG theo như tôi hiểu vấn đề của anh ấy, anh ấy đang nói về những người trợ giúp cho ứng dụng, và trong một ứng dụng chỉ có một ... uhm, ứng dụng.
Flavius

Lưu ý: những gì tôi đã trình bày thậm chí không phải là một mẫu singleton thực sự. Một singleton sẽ chỉ cho phép một thể hiện của một lớp bằng cách định nghĩa phương thức khởi tạo là private. Nó chỉ là một biến "toàn cục" có sẵn cho tất cả các phiên bản "Ứng dụng". Đó là lý do tại sao tôi nghĩ rằng nó hợp lệ KHÔNG bỏ qua các nguyên tắc tốt của OOP. Tất nhiên, như bất cứ điều gì trên thế giới, "khuôn mẫu" này cũng không nên được lạm dụng.
Flavius

-1 từ tôi nữa. Nó có thể chỉ là một nửa của Singleton DP, nhưng nó là một thứ xấu xí: "cung cấp quyền truy cập toàn cầu vào nó".
just somebody

2
Điều này thực sự làm cho cách tiếp cận hiện tại của anh ấy sạch sẽ hơn nhiều.
Daniel Von Fange

15

Tôi thích khái niệm về tiêm phụ thuộc:

"Dependency Injection là nơi các thành phần được cung cấp các thành phần phụ thuộc của chúng thông qua các hàm tạo, phương thức hoặc trực tiếp vào các trường. (Từ Trang web Pico Container )"

Fabien Potencier đã viết một loạt bài rất hay về Dependency Injection và nhu cầu sử dụng chúng. Anh ấy cũng cung cấp một Hộp chứa tiêm phụ thuộc nhỏ và xinh xắn có tên là Pimple mà tôi thực sự rất thích sử dụng (thông tin thêm trên github ).

Như đã nói ở trên, tôi không thích sử dụng Singletons. Bạn có thể tìm thấy một bản tóm tắt hay về lý do tại sao Singletons không có thiết kế tốt trong blog của Steve Yegge .


Tôi như việc thực hiện thông qua đóng cửa trong PHP, đọc rất thú vị
Juraj Blahunka

Tôi cũng vậy, anh ấy cũng có một số thứ cần thiết khác liên quan đến việc đóng cửa trên trang web của anh ấy: fabien.potencier.org/article/17/…
Thomas

2
chúng ta hãy hy vọng, đó webhouses chủ đạo sẽ di chuyển đến PHP 5.3 sớm, vì nó vẫn chưa phổ biến để xem một đầy đủ tính năng php 5.3 máy chủ
Juraj Blahunka

Họ sẽ phải làm vậy, khi ngày càng có nhiều dự án yêu cầu PHP 5.3 như Zend Framework 2.0 sẽ framework.zend.com/wiki/display/ZFDEV2/…
Thomas

1
Dependency injection cũng đã được chấp nhận câu trả lời về câu hỏi về decupling from GOD object: stackoverflow.com/questions/1580210/... với một ví dụ rất đẹp
Juraj Blahunka

9

Cách tiếp cận tốt nhất là có một số loại vật chứa cho các tài nguyên đó. Một số cách phổ biến nhất để triển khai vùng chứa này :

Singleton

Không được khuyến khích vì nó khó kiểm tra và ngụ ý trạng thái toàn cầu. (Viêm đơn bào)

Đăng ký

Loại bỏ singletonitis, lỗi Tôi cũng không khuyên bạn nên đăng ký, vì nó cũng là một loại singleton. (Khó kiểm tra đơn vị)

Di sản

Đáng tiếc, không có đa kế thừa trong PHP, vì vậy điều này giới hạn tất cả trong chuỗi.

Tiêm phụ thuộc

Đây là một cách tiếp cận tốt hơn, nhưng là một chủ đề lớn hơn.

Truyên thông

Cách đơn giản nhất để thực hiện việc này là sử dụng phương thức khởi tạo hoặc chèn bộ setter (truyền đối tượng phụ thuộc bằng cách sử dụng setter hoặc trong hàm tạo lớp).

Khung

Bạn có thể cuộn bộ tiêm phụ thuộc của riêng mình hoặc sử dụng một số khuôn khổ tiêm phụ thuộc, ví dụ. Yadif

Tài nguyên ứng dụng

Bạn có thể khởi tạo từng tài nguyên của mình trong bootstrap ứng dụng (hoạt động như một vùng chứa) và truy cập chúng ở bất cứ đâu trong ứng dụng truy cập đối tượng bootstrap.

Đây là phương pháp được triển khai trong Zend Framework 1.x

Trình tải tài nguyên

Một loại đối tượng tĩnh chỉ tải (tạo) tài nguyên cần thiết khi cần thiết. Đây là một cách tiếp cận rất thông minh. Bạn có thể thấy nó hoạt động, ví dụ như triển khai thành phần Dependency Injection của Symfony

Tiêm vào lớp cụ thể

Tài nguyên không phải lúc nào cũng cần thiết ở bất kỳ đâu trong ứng dụng. Đôi khi bạn chỉ cần chúng, ví dụ như trong bộ điều khiển (MV C ). Sau đó, bạn chỉ có thể đưa các tài nguyên vào đó.

Phương pháp phổ biến cho việc này là sử dụng nhận xét docblock để thêm siêu dữ liệu chèn.

Xem cách tiếp cận của tôi về vấn đề này tại đây:

Làm thế nào để sử dụng tính năng tiêm phụ thuộc trong Zend Framework? - Tràn ngăn xếp

Cuối cùng, tôi muốn thêm một lưu ý về điều rất quan trọng ở đây - bộ nhớ đệm.
Nói chung, bất chấp kỹ thuật bạn chọn, bạn nên nghĩ tài nguyên sẽ được lưu trữ như thế nào. Bộ nhớ cache sẽ là tài nguyên của chính nó.

Các ứng dụng có thể rất lớn và việc tải tất cả tài nguyên theo từng yêu cầu là rất tốn kém. Có nhiều cách tiếp cận, bao gồm máy chủ ứng dụng này - Máy chủ dự án trên Google Code .


6

Nếu bạn muốn cung cấp các đối tượng trên toàn cầu, mẫu đăng ký có thể thú vị đối với bạn. Để có cảm hứng, hãy xem Zend Registry .

Vì vậy, câu hỏi Registry so với Singleton cũng vậy .


Nếu bạn không muốn sử dụng Zend Framework, đây là một thực hiện tốt đẹp của mô hình đăng ký cho PHP5: phpbar.de/w/Registry
Thomas

Tôi thích một Mẫu đăng ký đã nhập, như Registry :: GetDatabase ("master"); Registry :: GetSession ($ user-> SessionKey ()); Đăng ký :: GetConfig ("cục bộ"); [...] và xác định giao diện cho từng loại. Bằng cách này, bạn đảm bảo rằng mình không vô tình ghi đè lên khóa được sử dụng cho một thứ khác (nghĩa là bạn có thể có "Cơ sở dữ liệu chính" và "Cấu hình chính". Bằng cách sử dụng Giao diện, bạn đảm bảo rằng chỉ các đối tượng hợp lệ mới được sử dụng. Điều này có thể cũng được thực hiện bằng cách sử dụng nhiều lớp Registry nhưng IMHO một trong những đơn là đơn giản và dễ sử dụng nhưng vẫn có những lợi thế.
Morfildur

Hoặc dĩ nhiên là một xây dựng vào PHP - $ _GLOBALS
Gnuffo1

4

Các đối tượng trong PHP chiếm một lượng lớn bộ nhớ, như bạn có thể đã thấy từ các bài kiểm tra đơn vị của mình. Do đó lý tưởng nhất là bạn nên tiêu hủy các đối tượng không cần thiết càng sớm càng tốt để tiết kiệm bộ nhớ cho các tiến trình khác. Với suy nghĩ đó, tôi thấy rằng mọi vật thể đều phù hợp với một trong hai khuôn.

1) Đối tượng có thể có nhiều phương thức hữu ích hoặc cần được gọi nhiều lần trong trường hợp đó tôi triển khai singleton / registry:

$object = load::singleton('classname');
//or
$object = classname::instance(); // which sets self::$instance = $this

2) Đối tượng chỉ tồn tại trong thời gian tồn tại của phương thức / hàm gọi nó, trong trường hợp đó, việc tạo đơn giản sẽ có lợi để ngăn các tham chiếu đối tượng kéo dài giữ các đối tượng tồn tại quá lâu.

$object = new Class();

Việc lưu trữ các đối tượng tạm thời BẤT CỨ ĐÂU có thể dẫn đến rò rỉ bộ nhớ vì các tham chiếu đến chúng có thể bị quên trong việc giữ đối tượng trong bộ nhớ cho phần còn lại của tập lệnh.


3

Tôi muốn hàm trả về các đối tượng đã khởi tạo:

A('Users')->getCurrentUser();

Trong môi trường thử nghiệm, bạn có thể xác định nó để trả về các mô hình. Bạn thậm chí có thể phát hiện bên trong ai gọi hàm bằng debug_backtrace () và trả về các đối tượng khác nhau. Bạn có thể đăng ký bên trong nó những người muốn lấy những đối tượng nào để có được một số thông tin chi tiết về những gì đang thực sự diễn ra bên trong chương trình của bạn.


-1

Tại sao không đọc hướng dẫn sử dụng?

http://php.net/manual/en/language.oop5.autoload.php


Cảm ơn gcb, nhưng việc tải các lớp không phải là mối quan tâm của tôi, câu hỏi của tôi có tính chất kiến ​​trúc hơn.
Pekka

Mặc dù về mặt lý thuyết, điều này có thể trả lời câu hỏi, nhưng tốt hơn hết bạn nên đưa các phần thiết yếu của câu trả lời vào đây và cung cấp liên kết để tham khảo.
jjnguy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.