搜尋
首頁後端開發php教程處理骨料根的收集 - 存儲庫模式

Handling Collections of Aggregate Roots – the Repository Pattern

核心要點

  • 領域驅動設計 (DDD) 中的倉儲模式充當領域模型和數據映射層之間的中介,增強數據查詢管理並最大限度地減少重複。
  • 倉儲將數據層的複雜性從領域模型中抽像出來,促進了關注點的清晰分離和持久化忽略,這符合 DDD 原則。
  • 實現倉儲涉及在類似集合的接口後面封裝數據訪問和操作的邏輯,這可以簡化與領域模型的交互。
  • 雖然倉儲在管理領域複雜性和隔離領域邏輯與數據持久化細節方面提供了顯著的好處,但對於簡單的應用程序來說,它們的實現可能過於復雜。
  • 倉儲的實際用途可以在需要復雜查詢和數據操作的系統中觀察到,在這些系統中,它們提供了更以領域為中心的語言,並減少了基礎結構洩漏到領域模型中。

傳統領域驅動設計 (DDD) 架構最典型的方面之一是領域模型展現的強制性持久化不可知性。在更保守的設計中,包括一些基於活動記錄或數據表網關的實現(為了追求相當具有欺騙性的簡單性,往往最終會用基礎設施污染領域邏輯),總是有一個底層存儲機制的明確概念,通常是關係數據庫。另一方面,領域模型從一開始就在概念上設計成嚴格的“存儲不可知”性質,從而將其任何持久化邏輯轉移到其邊界之外。即使考慮到 DDD 在直接引用“數據庫”時有些難以捉摸,但在現實世界中,很可能至少有一個數據庫在幕後運行,因為領域模型最終必須以某種形式持久化。因此,在模型和數據訪問層之間部署一個映射層是很常見的。這不僅積極推動保持各層之間相當程度的隔離,而且還保護了客戶端代碼中涉及在問題層的縫隙之間來回移動領域對象的每一個複雜細節。 Mea culpa 姑且不論,可以公平地說,處理數據映射器層中的奇異性是一項相當大的負擔,通常採用“編寫一次/永久使用”的策略。儘管如此,上述模式在相當簡單的條件下表現良好,在這些條件下,只有少量領域類由少量映射器處理。然而,當模型開始膨脹並變得越來越複雜時,情況可能會變得更加尷尬,因為隨著時間的推移,肯定會添加額外的映射器。這簡而言之表明,在使用由多個複雜聚合根組成的豐富的領域模型時,打開持久化忽略的大門在實踐中可能很難實現,至少如果不創建多個地方的昂貴對像圖或踏上重複實現的罪惡之路的話。更糟糕的是,在需要從數據庫中提取匹配不同條件的昂貴聚合根集合的大型系統中,如果未通過單個入口點正確集中,整個查詢過程本身就可能成為這種有缺陷的重複的積極、多產的推動者。在這種複雜的用例中,實現一個額外的抽象層(在 DDD 行話中通常稱為倉儲),它在數據映射器和領域模型之間進行仲裁,可以有效地幫助將查詢邏輯重複減少到最小,同時向模型公開真實內存集合的語義。然而,與映射器(它們是基礎設施的一部分)不同,倉儲本身的特點是使用模型的語言,因為它與模型緊密綁定。並且由於它對映射器的隱式依賴,它也保留了持久化忽略,因此提供了更高級別的抽象,更接近領域對象。令人遺憾的是,並非每個可能存在的應用程序都能輕易實現倉儲帶來的好處,因此只有在情況需要時,才值得實現它。無論如何,從頭開始構建一個小型的倉儲將非常有益,這樣您就可以看到它的內部工作原理,並揭示其相當深奧的外殼下究竟是什麼。

進行初步準備工作

實現倉儲的過程可能非常複雜,因為它實際上隱藏了在簡化的類似集合的 API 後注入和處理數據映射器的所有細節,而該 API 又會注入某種持久化適配器,等等。這種依賴項的連續注入,加上對大量邏輯的隱藏,解釋了為什麼倉儲通常被認為是一個簡單的外觀,即使一些觀點目前與該概念有所不同。無論哪種情況,為了啟動和運行功能性倉儲,我們應該採取的第一步是創建一個基本領域模型。我計劃在這裡使用的模型將負責對通用用戶進行建模,其基本結構如下所示:

<?php namespace Model;

interface UserInterface
{
    public function setId($id);
    public function getId();

    public function setName($name);
    public function getName();

    public function setEmail($email);
    public function getEmail();

    public function setRole($role);
    public function getRole();
}
<?php namespace Model;

class User implements UserInterface
{
    const ADMINISTRATOR_ROLE = "Administrator";
    const GUEST_ROLE         = "Guest";

    protected $id;
    protected $name;
    protected $email;
    protected $role;

    public function __construct($name, $email, $role = self::GUEST_ROLE) {
        $this->setName($name);
        $this->setEmail($email);
        $this->setRole($role);
    }

    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this user has been set already.");
        }
        if (!is_int($id) || $id             throw new InvalidArgumentException(
                "The user ID is invalid.");
        }
        $this->id = $id;
        return $this;
    }

    public function getId() {
        return $this->id;
    }

    public function setName($name) {
        if (strlen($name)  30) {
            throw new InvalidArgumentException(
                "The user name is invalid.");
        }
        $this->name = htmlspecialchars(trim($name), ENT_QUOTES);
        return $this;
    }

    public function getName() {
        return $this->name;
    }

    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The user email is invalid.");
        }
        $this->email = $email;
        return $this;
    }

    public function getEmail() {
        return $this->email;
    }

    public function setRole($role) {
        if ($role !== self::ADMINISTRATOR_ROLE
            && $role !== self::GUEST_ROLE) {
            throw new InvalidArgumentException(
                "The user role is invalid.");
        }
        $this->role = $role;
        return $this;
    }

    public function getRole() {
        return $this->role;
    }
}

在本例中,領域模型是一個相當骨骼的層,勉強高於能夠自我驗證的簡單數據持有者,它僅通過隔離的接口和簡單的實現者來定義一些虛構用戶的數 據和行為。為了保持簡潔易懂,我將保持模型的這種精簡程度。隨著模型已經在輕鬆隔離的情況下運行,讓我們通過向其添加另一個類來使其更豐富一些,該類負責處理用戶對象的集合。這個“附加”組件只是一個經典的數組包裝器,實現了 Countable、ArrayAccess 和 IteratorAggregate SPL 接口:

<?php namespace ModelCollection;
use MapperUserCollectionInterface,
    ModelUserInterface;

class UserCollection implements UserCollectionInterface
{
    protected $users = array();

    public function add(UserInterface $user) {
        $this->offsetSet($user);
    }

    public function remove(UserInterface $user) {
        $this->offsetUnset($user);
    }

    public function get($key) {
        return $this->offsetGet($key);
    }

    public function exists($key) {
        return $this->offsetExists($key);
    }

    public function clear() {
        $this->users = array();
    }

    public function toArray() {
        return $this->users;
    }

    public function count() {
        return count($this->users);
    }

    public function offsetSet($key, $value) {
        if (!$value instanceof UserInterface) {
            throw new InvalidArgumentException(
                "Could not add the user to the collection.");
        }
        if (!isset($key)) {
            $this->users[] = $value;
        }
        else {
            $this->users[$key] = $value;
        }
    }

    public function offsetUnset($key) {
        if ($key instanceof UserInterface) {
            $this->users = array_filter($this->users,
                function ($v) use ($key) {
                    return $v !== $key;
                });
        }
        else if (isset($this->users[$key])) {
            unset($this->users[$key]);
        }
    }

    public function offsetGet($key) {
        if (isset($this->users[$key])) {
            return $this->users[$key];
        }
    }

    public function offsetExists($key) {
        return ($key instanceof UserInterface)
            ? array_search($key, $this->users)
            : isset($this->users[$key]);
    }

    public function getIterator() {
        return new ArrayIterator($this->users);
    }
}

實際上,將這個數組集合放在模型的邊界內是完全可選的,因為使用普通數組可以產生幾乎相同的結果。然而,在本例中,通過依賴於獨立的集合類,可以更容易地通過面向對象的 API 訪問從數據庫中提取的用戶對象集。此外,考慮到領域模型必須完全忽略基礎設施中設置的底層存儲,我們應該採取的下一個邏輯步驟是實現一個映射層,使其與數據庫很好地分離。以下是構成此層的元素:

<?php namespace Model;

interface UserInterface
{
    public function setId($id);
    public function getId();

    public function setName($name);
    public function getName();

    public function setEmail($email);
    public function getEmail();

    public function setRole($role);
    public function getRole();
}
<?php namespace Model;

class User implements UserInterface
{
    const ADMINISTRATOR_ROLE = "Administrator";
    const GUEST_ROLE         = "Guest";

    protected $id;
    protected $name;
    protected $email;
    protected $role;

    public function __construct($name, $email, $role = self::GUEST_ROLE) {
        $this->setName($name);
        $this->setEmail($email);
        $this->setRole($role);
    }

    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this user has been set already.");
        }
        if (!is_int($id) || $id             throw new InvalidArgumentException(
                "The user ID is invalid.");
        }
        $this->id = $id;
        return $this;
    }

    public function getId() {
        return $this->id;
    }

    public function setName($name) {
        if (strlen($name)  30) {
            throw new InvalidArgumentException(
                "The user name is invalid.");
        }
        $this->name = htmlspecialchars(trim($name), ENT_QUOTES);
        return $this;
    }

    public function getName() {
        return $this->name;
    }

    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The user email is invalid.");
        }
        $this->email = $email;
        return $this;
    }

    public function getEmail() {
        return $this->email;
    }

    public function setRole($role) {
        if ($role !== self::ADMINISTRATOR_ROLE
            && $role !== self::GUEST_ROLE) {
            throw new InvalidArgumentException(
                "The user role is invalid.");
        }
        $this->role = $role;
        return $this;
    }

    public function getRole() {
        return $this->role;
    }
}

開箱即用,UserMapper 執行的任務批次相當簡單,僅限於公開幾個通用查找器,這些查找器負責從數據庫中提取用戶並通過 createUser() 方法重建相應的實體。此外,如果您之前已經深入研究了一些映射器,甚至編寫了自己的映射傑作,那麼上述內容肯定很容易理解。唯一值得強調的微妙細節可能是 UserCollectionInterface 已被放置在映射層中,而不是在模型中。我故意這樣做,因為這樣,用戶集合所依賴的抽象(協議)是由更高級別的 UserMapper 明確聲明和擁有的,這與依賴倒置原則所推廣的指南一致。隨著映射器已經設置,我們可以直接開箱即用,並從存儲中提取一些用戶對象,以便立即使模型水化。雖然乍一看這似乎確實是正確的路徑,但實際上我們會在不必要地用基礎設施污染應用程序邏輯,因為映射器實際上是基礎設施的一部分。如果將來需要根據更精細的、特定於領域的條件(不僅僅是映射器的查找器公開的通用條件)來查詢用戶實體,該怎麼辦?在這種情況下,確實需要在映射層之上放置一個額外的層,它不僅會提供更高級別的數據訪問,而且還會通過一個單點來攜帶查詢邏輯塊。最終,這就是我們期望從倉儲獲得的大量好處。

實現用戶倉儲

在生產環境中,倉儲可以在其表面實現人們可以想到的幾乎所有內容,以便向模型公開聚合根的內存集合的錯覺。然而,在本例中,我們不能如此天真地期望免費享受這種昂貴的奢侈品,因為我們將要構建的倉儲將是一個相當人為的結構,負責從數據庫中提取用戶:

<?php namespace ModelCollection;
use MapperUserCollectionInterface,
    ModelUserInterface;

class UserCollection implements UserCollectionInterface
{
    protected $users = array();

    public function add(UserInterface $user) {
        $this->offsetSet($user);
    }

    public function remove(UserInterface $user) {
        $this->offsetUnset($user);
    }

    public function get($key) {
        return $this->offsetGet($key);
    }

    public function exists($key) {
        return $this->offsetExists($key);
    }

    public function clear() {
        $this->users = array();
    }

    public function toArray() {
        return $this->users;
    }

    public function count() {
        return count($this->users);
    }

    public function offsetSet($key, $value) {
        if (!$value instanceof UserInterface) {
            throw new InvalidArgumentException(
                "Could not add the user to the collection.");
        }
        if (!isset($key)) {
            $this->users[] = $value;
        }
        else {
            $this->users[$key] = $value;
        }
    }

    public function offsetUnset($key) {
        if ($key instanceof UserInterface) {
            $this->users = array_filter($this->users,
                function ($v) use ($key) {
                    return $v !== $key;
                });
        }
        else if (isset($this->users[$key])) {
            unset($this->users[$key]);
        }
    }

    public function offsetGet($key) {
        if (isset($this->users[$key])) {
            return $this->users[$key];
        }
    }

    public function offsetExists($key) {
        return ($key instanceof UserInterface)
            ? array_search($key, $this->users)
            : isset($this->users[$key]);
    }

    public function getIterator() {
        return new ArrayIterator($this->users);
    }
}
<?php namespace Mapper;
use ModelUserInterface;

interface UserCollectionInterface extends Countable, ArrayAccess, IteratorAggregate 
{
    public function add(UserInterface $user);
    public function remove(UserInterface $user);
    public function get($key);
    public function exists($key);
    public function clear();
    public function toArray();
}
<?php namespace Mapper;
use ModelRepositoryUserMapperInterface,  
    ModelUser;

class UserMapper implements UserMapperInterface
{    
    protected $entityTable = "users";
    protected $collection;

    public function __construct(DatabaseAdapterInterface $adapter, UserCollectionInterface $collection) {
        $this->adapter = $adapter;
        $this->collection = $collection;
    }

    public function fetchById($id) {
        $this->adapter->select($this->entityTable,
            array("id" => $id));
        if (!$row = $this->adapter->fetch()) {
            return null;
        }
        return $this->createUser($row);
    }

    public function fetchAll(array $conditions = array()) {
        $this->adapter->select($this->entityTable, $conditions);
        $rows = $this->adapter->fetchAll();
        return $this->createUserCollection($rows);

    }

    protected function createUser(array $row) {
        $user = new User($row["name"], $row["email"],
            $row["role"]);
        $user->setId($row["id"]);
        return $user;
    }

    protected function createUserCollection(array $rows) {
        $this->collection->clear();
        if ($rows) {
            foreach ($rows as $row) {
                $this->collection[] = $this->createUser($row);
            }
        }
        return $this->collection;
    }
}

儘管位於一個相當輕量級的結構之上,但 UserRepository 的實現非常直觀,因為它的 API 允許它從符合與模型語言密切相關的精細謂詞的存儲中提取用戶對象集合。此外,在其當前狀態下,倉儲僅向客戶端代碼公開一些簡單的查找器,而客戶端代碼又利用數據映射器的功能來訪問存儲。在更現實的環境中,倉儲還應該能夠持久化聚合根。如果您想向 UserRepository 中添加 insert() 方法或其他類似的方法,請隨意這樣做。無論哪種情況,通過示例來捕捉使用倉儲的實際優勢的一種有效方法是:

<?php namespace Model;

interface UserInterface
{
    public function setId($id);
    public function getId();

    public function setName($name);
    public function getName();

    public function setEmail($email);
    public function getEmail();

    public function setRole($role);
    public function getRole();
}

如前所述,倉儲有效地將業務術語與客戶端代碼(Eric Evans 在他的著作《領域驅動設計》中創造的所謂“普遍語言”)互換,而不是較低級別的技術術語。與數據映射器查找器中存在的模糊性不同,另一方面,倉儲的方法用“名稱”、“電子郵件”和“角色”來描述自身,這些無疑是建模用戶實體的屬性的一部分。這種更精細的更高級別的數據抽象,以及在封裝複雜系統中的查詢邏輯時所需的一整套功能,無疑是使倉儲在多層設計中更具吸引力的最令人信服的原因之一。當然,大多數時候,在預先獲得這些好處和部署額外的抽象層(在更簡單的應用程序中可能過於臃腫)的麻煩之間存在隱含的權衡。

結束語

作為領域驅動設計中的核心概念之一,倉儲可以在用其他幾種語言(例如 Java 和 C#,僅舉幾例)編寫的應用程序中找到。然而,在 PHP 中,它們仍然相對未知,只是在世界上邁出了第一步。儘管如此,還是有一些值得信賴的框架,例如 FLOW3 和當然還有 Doctrine 2.x,可以幫助您採用 DDD 範例。與任何現有的開發方法一樣,您不必在您的應用程序中使用倉儲,甚至不必不必要地將它們與 DDD 背後的概念堆一起粉碎。只需運用常識,只有在您認為它們適合您的需求時才選擇它們。就這麼簡單。 圖片來自 Chance Agrella / Freerangestock.com

關於處理聚合根集合的常見問題解答 (FAQ)

什麼是領域驅動設計中的聚合根?

在領域驅動設計 (DDD) 中,聚合根是一組關聯對象的集合,被視為一個單元。這些對象由根實體(也稱為聚合根)綁定在一起。聚合根通過禁止外部對象持有對其成員的引用來維護正在對聚合進行的更改的一致性。

聚合根與普通實體有何不同?

聚合根與普通實體之間的主要區別在於它們的職責。普通實體封裝行為和狀態,而聚合根還通過控制對其成員的訪問來確保整個聚合的完整性。它是聚合中唯一允許外部對象持有對其引用的成員。

如何在我的領域模型中識別聚合根?

識別聚合根需要深入了解業務領域。它通常是一個高級實體,具有全局標識並封裝其他實體和值對象。例如,在電子商務領域,訂單可以是一個聚合根,它封裝行項目和送貨信息。

如何處理聚合根的集合?

處理聚合根的集合可能具有挑戰性。重要的是要記住,每個聚合根都是一個一致性邊界,因此對一個聚合根的更改不應影響其他聚合根。因此,在處理集合時,通常最好分別加載和持久化每個聚合根以保持一致性。

聚合根可以引用另一個聚合根嗎?

是的,聚合根可以引用另一個聚合根,但它應該只通過標識來引用。這意味著它不應持有對另一個聚合根對象的直接引用,而應持有其 ID。這有助於維護每個聚合根的一致性邊界。

聚合根如何在 DDD 中與倉儲相關?

在 DDD 中,倉儲提供檢索和存儲聚合根的方法。它抽象了底層存儲機制,允許領域模型忽略數據持久化的細節。每個聚合根通常都有自己的倉儲。

聚合根在執行業務規則中的作用是什麼?

聚合根在執行業務規則中起著至關重要的作用。它確保對聚合的所有更改都使其處於有效狀態。這意味著任何跨越多個實體或值對象的業務規則都應由聚合根強制執行。

聚合根如何有助於降低領域模型的複雜性?

通過充當一致性邊界並控制對其成員的訪問,聚合根有助於降低領域模型的複雜性。它通過為每個聚合提供一個單一交互點來簡化模型,從而更容易理解系統。

聚合根可以是多個聚合的一部分嗎?

不可以,聚合根不應是多個聚合的一部分。這將違反聚合的一致性邊界,並可能導致領域模型不一致。

如何處理聚合根的並發問題?

可以使用各種策略來處理聚合根的並發問題,例如樂觀鎖或悲觀鎖。策略的選擇取決於應用程序的具體要求以及您面臨的並發問題的性質。

This revised output maintains the original image formatting and location, paraphrases the text to avoid plagiarism, and keeps the core meaning intact. Remember to always cite your sources appropriately.

以上是處理骨料根的收集 - 存儲庫模式的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
PHP和Python:解釋了不同的範例PHP和Python:解釋了不同的範例Apr 18, 2025 am 12:26 AM

PHP主要是過程式編程,但也支持面向對象編程(OOP);Python支持多種範式,包括OOP、函數式和過程式編程。 PHP適合web開發,Python適用於多種應用,如數據分析和機器學習。

PHP和Python:深入了解他們的歷史PHP和Python:深入了解他們的歷史Apr 18, 2025 am 12:25 AM

PHP起源於1994年,由RasmusLerdorf開發,最初用於跟踪網站訪問者,逐漸演變為服務器端腳本語言,廣泛應用於網頁開發。 Python由GuidovanRossum於1980年代末開發,1991年首次發布,強調代碼可讀性和簡潔性,適用於科學計算、數據分析等領域。

在PHP和Python之間進行選擇:指南在PHP和Python之間進行選擇:指南Apr 18, 2025 am 12:24 AM

PHP適合網頁開發和快速原型開發,Python適用於數據科學和機器學習。 1.PHP用於動態網頁開發,語法簡單,適合快速開發。 2.Python語法簡潔,適用於多領域,庫生態系統強大。

PHP和框架:現代化語言PHP和框架:現代化語言Apr 18, 2025 am 12:14 AM

PHP在現代化進程中仍然重要,因為它支持大量網站和應用,並通過框架適應開發需求。 1.PHP7提升了性能並引入了新功能。 2.現代框架如Laravel、Symfony和CodeIgniter簡化開發,提高代碼質量。 3.性能優化和最佳實踐進一步提升應用效率。

PHP的影響:網絡開發及以後PHP的影響:網絡開發及以後Apr 18, 2025 am 12:10 AM

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP類型提示如何起作用,包括標量類型,返回類型,聯合類型和無效類型?PHP類型提示如何起作用,包括標量類型,返回類型,聯合類型和無效類型?Apr 17, 2025 am 12:25 AM

PHP類型提示提升代碼質量和可讀性。 1)標量類型提示:自PHP7.0起,允許在函數參數中指定基本數據類型,如int、float等。 2)返回類型提示:確保函數返回值類型的一致性。 3)聯合類型提示:自PHP8.0起,允許在函數參數或返回值中指定多個類型。 4)可空類型提示:允許包含null值,處理可能返回空值的函數。

PHP如何處理對象克隆(克隆關鍵字)和__clone魔法方法?PHP如何處理對象克隆(克隆關鍵字)和__clone魔法方法?Apr 17, 2025 am 12:24 AM

PHP中使用clone關鍵字創建對象副本,並通過\_\_clone魔法方法定制克隆行為。 1.使用clone關鍵字進行淺拷貝,克隆對象的屬性但不克隆對象屬性內的對象。 2.通過\_\_clone方法可以深拷貝嵌套對象,避免淺拷貝問題。 3.注意避免克隆中的循環引用和性能問題,優化克隆操作以提高效率。

PHP與Python:用例和應用程序PHP與Python:用例和應用程序Apr 17, 2025 am 12:23 AM

PHP適用於Web開發和內容管理系統,Python適合數據科學、機器學習和自動化腳本。 1.PHP在構建快速、可擴展的網站和應用程序方面表現出色,常用於WordPress等CMS。 2.Python在數據科學和機器學習領域表現卓越,擁有豐富的庫如NumPy和TensorFlow。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前By尊渡假赌尊渡假赌尊渡假赌
威爾R.E.P.O.有交叉遊戲嗎?
1 個月前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。