最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

9個步驟帶你了解PHP如何安全地遷移數(shù)據(jù)庫或字段

 更新時間:2025年11月23日 08:40:47   作者:BingoGo  
這篇文章主要為大家詳細(xì)介紹了如何通過9個步驟了解PHP如何安全地遷移數(shù)據(jù)庫或字段,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

問題描述

這篇文章要講的是一個非常具體且棘手的問題:唯一 ID 遷移。

現(xiàn)在有一個實體 User,由 User::$id 標(biāo)識,看起來像這樣:

final class User
{
  public function __construct(
    public int $id,
  ) {}
}

訪問它的數(shù)據(jù)的方式是通過一個名為 UserRepository 的倉儲接口。這里提供一個簡單的 SQLite 實現(xiàn):

interface UserRepository
{
  /**
   * @throws  UserNotFoundException
   */
  public function findById(
    int $id
  ): User;
}

final class SqliteUserRepository
  implements UserRepository
{
  public function findById(
    int $id
  ): User {
    $sql = "...";
    $stmt = $this->pdo->prepare($sql);
    $stmt->execute([
      'id' => $id,
    ]);
    // ...
  }
}

非常簡單的設(shè)置。

現(xiàn)在假設(shè)你的團隊決定,出于安全原因,用戶的唯一標(biāo)識符不應(yīng)再是整數(shù),而應(yīng)該采用 UUID。

當(dāng)然,不允許停機。

解決方案

為了絕對安全,將分三個階段實施:

  • 讓兩個 ID 共存
  • 實戰(zhàn)測試 UUID 實現(xiàn)
  • 淘汰以前的整數(shù) ID 實現(xiàn)

分階段進行的主要原因是,測試不足以確保一切都按預(yù)期工作。這個字段可能被其他作業(yè)通過 API 使用,或者我現(xiàn)在甚至無法想象的東西。

所以以防萬一,我希望能夠在任何時刻安全地回滾到以前的實現(xiàn)。

假設(shè)這里描述的每一步都有適當(dāng)?shù)臏y試覆蓋,理想情況下是在重構(gòu)發(fā)生之前。

步驟 1 - 從原始類型解耦

無論你接下來做什么,沒有這個都不容易!User 類中的那個 int 原始類型就是在要求爆炸發(fā)生。

如果你想從整數(shù)平滑過渡到 UUID,最好的辦法是首先將代碼與原始類型解耦。一種方法是封裝你的原始類型。我將創(chuàng)建一個名為 UserId 的類,讓代碼依賴它而不是 int

final class UserId
{
  public function __construct(
    public int $id,
  ) {}

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

final class User
{
  public function __construct(
    public UserId $id,
  ) {}
}

interface UserRepository
{
  /**
   * @throws  UserNotFoundException
   */
  public function findById(
    UserId $id
  ): User;
}

上面的代碼應(yīng)該會使重構(gòu)稍微容易一些。當(dāng)調(diào)用 getId() 時,UserId 仍然返回 int,但這沒關(guān)系!最重要的是,我們的代碼依賴于 UserId——一個我們控制的類型——而不是原始整數(shù)——我們根本無法控制它。

現(xiàn)在只需覆蓋所有現(xiàn)有代碼以使用 UserId 而不是 int $id。

final class SqliteUserRepository
  implements UserRepository
{
  public function findById(
    UserId $id
  ): User {
    $sql = "...";
    $stmt = $this->pdo->prepare($sql);
    $stmt->execute([
      'id' => $id->getId(),
    ]);
    // ...
  }
}

到這里,什么都沒有改變。感覺可以安全地合并和部署,不應(yīng)該有任何東西會崩潰。順便說一句,測試幫助很大。一定要進行測試!

步驟 2 - 讓兩個字段共存

現(xiàn)在確保可以向 users 表添加一個新字段。這樣就可以了:

sqlite> ALTER TABLE `users` ADD `uuid` VARCHAR;

現(xiàn)在它既不能是 NOT NULL 也不能是 UNIQUE,因為每個現(xiàn)有記錄的值都將是 NULL。

回到 UserId 類,確保它現(xiàn)在的實現(xiàn)中有 uuid

final class UserId
{
  public function __construct(
    public int $id,
    public ?UuidInterface $uuid,
  ) {}

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

  public function getUuid(): ?UUidInterface
  {
    return $this->uuid;
  }
}

它仍然是可空的,因為,嗯,它在數(shù)據(jù)庫中是 null!

現(xiàn)在需要確保發(fā)生兩件事:

  • 每個現(xiàn)有記錄都將有一個非空的 uuid;并且
  • 每個新記錄都將已經(jīng)帶有填充的 uuid

當(dāng)看到在任何給定時刻,users.uuid 永遠(yuǎn)不會是 NULL 時,則認(rèn)為兩者在數(shù)據(jù)庫層都很好地共存。

步驟 3 - 確保每個新記錄都有 UUID

在你的系統(tǒng)中,某個地方存儲著 Users。需要確保在它發(fā)生的任何地方,UUID 字段都將被填充。

所以給定這個舊的實現(xiàn):

public function insert(
  User $user
): void {
  // insert into ...
}

只需用 UUID 生成來修補它,應(yīng)該就沒問題了:

public function insert(
  User $user
): void {
  $id = $user->id;

  if ($id->uuid === null) {
    $id->uuid = Uuid::uuid4();
  }

  // insert into ...
}

個人強烈建議你用測試覆蓋這個 IF 語句,以防你遺漏了導(dǎo)入或類似的東西。除此之外,不應(yīng)該引入其他回歸。

每個新記錄現(xiàn)在應(yīng)該都有正確填充的 users.uuid。

步驟 4 - 為舊記錄回填 UUID 字段

這可以用腳本完成。如果你使用遷移框架,可能也會非常簡單。

現(xiàn)在只需要獲取所有 uuid 為 null 的用戶并填充它們。類似這樣就可以完成:

$users = getUsersWithEmptyUuid();
foreach ($users as $user) {
  $user->id->uuid = Uuid::uuid4();
  updateUser($user);
}

上面的代碼并不能代表每個代碼庫,但我想你明白了。

步驟 5 - 確保一切正常運行

不要急于切換實現(xiàn)。一定要確保系統(tǒng)正常運行,并且在再運行系統(tǒng)幾個小時后,users.uuid 不會是 NULL。

只有當(dāng)你 100% 確定 users.uuid 在此表中永遠(yuǎn)不會是 NULL 時,才進入下一步。

步驟 6 - 更新 UserRepository 以使用 UUID

看來現(xiàn)在已經(jīng)可以切換到新的 UUID 實現(xiàn)了。但不建議盲目地切換到新實現(xiàn)。

謹(jǐn)慎總比后悔好,對吧?首先確保用功能開關(guān)保護代碼。用以下內(nèi)容更新 SqliteUserRepository

final class SqliteUserRepository
    implements UserRepository
{
  public function findById(UserId $id): User
  {
    if (
      isFeatureFlagActive('enableNewUsersUuidImplementation')
    ) {
      // 新實現(xiàn),使用 Uuid
      $sql = "...";
      $stmt = $this->pdo->prepare($sql);
      $stmt->execute([
        'uuid' => (string) $id->getUuid(),
      ]);
      // ...
    } else {
      // 舊實現(xiàn),使用整數(shù) $id
      $sql = "...";
      $stmt = $this->pdo->prepare($sql);
      $stmt->execute([
        'id' => $id->getId(),
      ]);
      // ...
    }
  }
}

長話短說:如果請求的功能已啟用,isFeatureFlagActive() 返回 TRUE,否則返回 FALSE。它可以基于配置、數(shù)據(jù)庫條目或環(huán)境變量。這在這里不相關(guān)。

重要的是,你可以更改 isFeatureFlagActive() 的返回值,而無需重新部署代碼。這樣你就可以安全地回滾到以前的實現(xiàn),沒有太多摩擦。

步驟 7 - 部署、啟用和監(jiān)控

首先部署它,確保 isFeatureFlagActive() 始終返回 FALSE,這樣就會選擇原始實現(xiàn)。

然后將 isFeatureFlagActive() 切換為返回 TRUE,這樣就會選擇新實現(xiàn)——同樣,這可以通過數(shù)據(jù)庫記錄、環(huán)境變量、SaaS 工具或你喜歡的任何東西來完成。

哦不!出問題了!網(wǎng)站突然變得超級慢?。?/p>

關(guān)閉你的功能開關(guān),這樣 isFeatureFlagActive() 將再次返回 FALSE。

...

事情似乎又恢復(fù)正常了?;氐侥愕?IDE,試著弄清楚發(fā)生了什么。也許做一些點擊測試和調(diào)試來理解是什么導(dǎo)致它如此緩慢。

最終你會意識到你沒有索引 users.uuid 列,所以由于你的巨大表,查詢它變得超級慢。盡快修復(fù)它!

步驟 8 - 使 UUID 唯一并建立索引

由于使用的是 SQLite 實現(xiàn),這里是應(yīng)該完成此操作的代碼片段:

sqlite> CREATE UNIQUE INDEX `users_uuid_uq` ON `users`(`uuid`);

理想情況下,你還應(yīng)該使 users.uuidNOT NULL,但我跳過了它,因為它需要更多的 SQLite 步驟,這些步驟與我想在這里演示的內(nèi)容無關(guān)。

好了,現(xiàn)在應(yīng)該沒問題了。將你的更改傳播到生產(chǎn)環(huán)境,看看功能開關(guān)的代碼現(xiàn)在表現(xiàn)如何。

一切都好,對吧?是時候清理了。

步驟 9 - 清理你的數(shù)字 ID

既然東西已經(jīng)部署并經(jīng)過實戰(zhàn)測試,是時候清理以前的數(shù)字 id 字段了。

無論你是刪除實際字段還是只是不在代碼中使用它,這都是項目決策——什么不是呢?

但最終你的 SqliteUserRepository 會看起來像這樣:

final class SqliteUserRepository
    implements UserRepository
{
  public function findById(
    UserId $id
  ): User {
    $sql = "...";
    $stmt = $this->pdo->prepare($sql);
    $stmt->execute([
      'uuid' => (string) $id->getUuid(),
    ]);
    // ...
  }
}

插入記錄的函數(shù)現(xiàn)在也值得一些關(guān)愛。讓我們刪除以前的 IF 語句:

...

public function insert(
  User $user
): void {
  $user->id->uuid = Uuid::uuid4();

  // insert logic
}

...

如果你決定也從數(shù)據(jù)庫中刪除數(shù)字 id,必須確保 UserId 代碼也被清理,并刪除 $id 屬性:

final class UserId
{
  public function __construct(
    public UuidInterface $uuid,
  ) {}

  public function getUuid(
  ): UuidInterface {
    return $this->uuid;
  }
}

因為現(xiàn)在 UUID 完全沒有理由為空,也從 $uuid 屬性中刪除了問號。現(xiàn)在你的系統(tǒng)是安全的!

總結(jié)

當(dāng)然,事情可能因項目而異,但歸根結(jié)底,你將執(zhí)行所描述技術(shù)的某種變體。

這適用于幾乎任何依賴數(shù)據(jù)的實現(xiàn)更改。只需記住三個階段:

  • 讓兩個實現(xiàn)共存
  • 實戰(zhàn)測試新實現(xiàn)
  • 淘汰以前的實現(xiàn)

不要害羞或羞于采取多個步驟。即使你知道之后必須刪除代碼!實際上回滾部署或修復(fù)實時數(shù)據(jù)庫比在這里描述的任何步驟都要痛苦得多。

到此這篇關(guān)于9個步驟帶你了解PHP如何安全地遷移數(shù)據(jù)庫或字段的文章就介紹到這了,更多相關(guān)PHP遷移數(shù)據(jù)庫或字段內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

玉屏| 洪泽县| 工布江达县| 霍林郭勒市| 手游| 浏阳市| 安西县| 互助| 汉中市| 镇坪县| 寿光市| 永定县| 裕民县| 五寨县| 万山特区| 唐海县| 梓潼县| 宁波市| 称多县| 开远市| 田阳县| 沁阳市| 荆州市| 绥化市| 蓬安县| 温泉县| 沂水县| 白玉县| 丰台区| 翁牛特旗| 招远市| 平利县| 合肥市| 河源市| 鲜城| 本溪市| 乐东| 阳春市| 玉门市| 海晏县| 鲁山县|