Yii2框架那些折磨人的坑

来源:https://www.cnblogs.com/zydj333/archive/2019/12/15/12038025.html
-Advertisement-
Play Games

說點閑話 距離上次寫博客,已經有一年了。在動手寫之前,總是帶著深深的罪惡感。被它折磨許久,終於,還是,動手了。 值得慶祝的一件事:最近開始健身了。每天動感單車45分鐘,游泳45分鐘,真的是(生)爽(不)到(如)爆(死)。 好了,扯淡完畢,步入正題。 ActiveRecord被莫名寫入? 準備知識 代 ...


說點閑話

距離上次寫博客,已經有一年了。在動手寫之前,總是帶著深深的罪惡感。被它折磨許久,終於,還是,動手了。

值得慶祝的一件事:最近開始健身了。每天動感單車45分鐘,游泳45分鐘,真的是(生)爽(不)到(如)爆(死)。

好了,扯淡完畢,步入正題。


 

ActiveRecord被莫名寫入?

準備知識

  1. ActiveRecord的基本用法。如果不理解,可參考這裡

代碼現場

/**
 * @property integer $id
 * @property string $name
 * @property string $detail
 * @property double $price
 * @property integer $area
 **/
class OcRoom extends ActivieRecord
{
    ...
}

$room = OcRoom::find()      //先取出一個對象。
    ->select(['id'])        //只取出'id'列
    ->where(['id'=>20])
    ->one();
$room->save();              //保存,會發現此行的其它欄位都被寫成預設值了。

總結問題

這個例子的問題在於:

  1. 我從資料庫中取出了一行,也就是代碼中的$room,但是只取出了id欄位,而其他欄位自然就是預設值。
  2. 當我$room->save()的時候,那些是預設值的欄位也被保存到資料庫里去了。what!?
  3. 也就是說,當你想節約資源,不取出所有欄位的時候,一定要註意不能保存,否則,很多數據會被莫名修改為預設值。

解決方法

然而,我們有什麼解決辦法呢?提供幾種思路:

  1. 自己時刻註意,避免未完全取出的ActiveRecord的保存。
  2. 修改或繼承ActiveRecord, 使得,當此對象由find()新建,且欄位沒有完全取出,調用save()方法,拋出異常。
  3. 修改或繼承ActiveRecord,使得,當此對象由find()新建,且欄位沒有完全取出,調用save()方法時,只保存取出過的欄位,其他欄位被忽略。

 

你的Transaction生效了嗎?

代碼現場

/**
 * @property integer $id
 * @property string $name
 **/
class OcRoom extends ActiveRecord
{
    public function rules()
    {
        return [['name','string','min'=>2,'max'=>10]];
    }
    ...
}
class OcHouse extends ActiveRecord
{
    public function rules()
    {
        return [['name','string','max'=>10]];
    }
    ...
}

$a = new OcRoom();
$a->name = '';                //name為空字元串,不滿足rules()條件。

$b = new OcHouse();
$b->name = '我的房間';         //name合法,可以保存。

$transaction = Yii::$app->db->beginTransaction();
try{
    $a->save();               //name欄位不合法,無法驗證通過,在validate()階段已經返回false,不會進行資料庫存儲的步驟,所以也不會拋出異常。
    $b->save();               //name欄位合法,可以正常保存。

    $transaction->commit();   //提交後,發現$a保存失敗,而$b保存成功。
}
catch (Exception $e) 
{
    Yii::error($e->getTraceAsString(),__METHOD__);
    $transaction->rollBack();
}

問題總結

這段代碼的問題在於:

  1. 大家知道$transaction的存在意義是保證整段資料庫存儲代碼要麼全成功,要麼全失敗。
  2. 顯然,在這個例子中,transaction並沒有達到我們想要的效果:$a因為validate()都沒過,所以$transation->commit()的時候並不會報錯。

解決方法

$transation塊內,所有的save()都要判斷下返回值,如果為false,則直接拋出異常。


 

'Y-m-d'不被識別?

代碼現場

OcRenterBill extends ActiveRecord
{
    public function rules()
    {
        return [
            ['start_time','date','format'=>'Y-m-d'],
        ];
    }
}

$a = new OcRenterBill();
$a = '2015-09-12';
$a->save();                 //會報錯,說格式不對

問題總結

如果一開始,Yii框架就報錯,這個還不算坑。坑的是我在Mac上開發時,這個可以完全正常的工作,而發佈到線上環境(Ubuntu)後,就彈出“屬性start_time格式無效”的錯誤。而參考官方文檔,發現這種格式是允許的官方文檔

啊啊啊。各種試錯,最後發現如果改成php:Y-m-d,世界就清凈了。所以,如果你遇到這種問題,感激我吧。


 

記憶體泄露

代碼現場

public static function actionTest() {
        $total = 10;
        var_dump('開始記憶體'.memory_get_usage());
        while($total){
            $ret=User::findOne(['id'=>910002]);
            var_dump('end記憶體'.memory_get_usage());
            unset($ret);
            $total--;
        }
    }

上面代碼的記憶體一直在增長, 按照原本想法來看, 變數被釋放了,記憶體就算增長也不會一直增長。因為每迴圈一次記憶體都會被釋放。

分析問題 上面這段代碼涉及到了資料庫的操作,而我們知道,資料庫的很多地方都能引起記憶體泄漏。 所以先屏蔽資料庫相關操作, 我手寫了一個原生的資料庫查詢操作, 發現記憶體正常,沒有問題。

$dsn = "mysql:dbname=test;host=localhost";
$db_user = 'root';
$db_pass = 'admin';
//查詢
$sql = "select * from buyer";
$res = $pdo->query($sql);
foreach($res as $row) {
    echo $row['username'].'<br/>';
}

這時候答案呼之欲出--- 是yii2框架搞了鬼

定位問題 既然知道了是yii2 框架的問題那就可以進一步縮小問題。

public static function actionTest() {
        $total = 10;
        var_dump('開始記憶體'.memory_get_usage());
        while($total){
            $ret= new User();
            var_dump('end記憶體'.memory_get_usage());
            unset($ret);
            $total--;
        }
    }

記憶體還是一直增長。 這時候我測試了一個其他的yii2類 發覺記憶體不增長了。 這就可以聯想到是在new 對象的時候yii2內部自己執行了什麼操作,然後導致記憶體泄漏。 什麼方法是new 的時候就執行的呢。。。 對的 構造方法 __construct 。 然後 我一步一步的從model 查到object 發覺都沒有能引起泄漏的地方。

這個時候我們不妨換個思路, 既然是yii2框架下出現的泄漏, 那肯定就是yii2獨有的功能, 那什麼功能是yii2獨有的,又是在new 對象的時候就會執行的呢?

行為(Behavior) 發覺我的模型類裡面果然有用了行為

public function behaviors()
    {
        return [
            TimestampBehavior::class,
        ];
    }

最普通不過的代碼。 我們知道 行為最後調用的地方是 yii\base\Component->attachBehaviors 最後定位到

private function attachBehaviorInternal($name, $behavior)
    {
        if (!($behavior instanceof Behavior)) {
            $behavior = Yii::createObject($behavior);
        }
        if (is_int($name)) {
            $behavior->attach($this);
            $this->_behaviors[] = $behavior;
        } else {
            if (isset($this->_behaviors[$name])) {
                $this->_behaviors[$name]->detach();
            }
            $behavior->attach($this);
            $this->_behaviors[$name] = $behavior;
        }
 
        return $behavior;
    }

我們觀察這段代碼,發覺他把自己傳進去了$behavior->attach($this); 最後調用的是 yii\base\Behavior->attach

public function attach($owner)
    {
        $this->owner = $owner;
        foreach ($this->events() as $event => $handler) {
            $owner->on($event, is_string($handler) ? [$this, $handler] : $handler);
        }
    }

問題總結

這個時候答案已經呼之欲出, Yii2為了實現行為這一功能, 把自身this傳進去,以便能註冊事件、觸發事件、解除事件。 這就導致了一個迴圈引用的問題。 所以導致對象refcount一直不為0 一直回收不了。

接下來就好辦了。將查詢換成原始的連接試試。果然,記憶體上升的非常慢了,可以說這才是正常現象。現在的記憶體也就是50m左右,cpu也穩定在7%左右。 

代碼優化後,再跑腳本,1分鐘左右吧,腳本就跑完了。重點是不會再報出記憶體錯誤了。所以,以後考慮問題還是要深入。敢於質疑。以後如果遇到這種記憶體錯誤,一定要先檢查自己的代碼是不是有記憶體泄漏的地方。不要想著先設置php的記憶體。這樣只會治標不治本。

總結

1、從開發速度方面,藉助於gii腳手架,可以快速生成代碼,也就是說搭建一個可以增刪改查的系統可能一行代碼都不用寫,而且集成了jquery和bootstrap,特效和樣式基本也不需要寫了,這對於設計和審美能力普遍較差的後端程式員來說簡直是一大福利。不過在前後端完全的分離的趨勢下,Yii2前後端的耦合的還是有些重了。

2、從代碼的可讀性方面,Yii不會為了刻板地遵照某種設計模式而對代碼進行過度的設計。基本上類在IDE里不藉助第三方組件是可以跳轉閱讀源碼的。這點上Yii要比Laravel略勝一籌。

3、從開源生態圈方面,Yii因為人少,稍微偏門一點的資料就很少,需要強大的谷歌能力和閱讀英文文檔的能力。

不可否認,Yii是一個優秀的開發框架,值得PHP開發者上手學習,踩坑的過程也是一種成長與積累。最後祝願PHP小伙伴們都健健康康,事業有成。

 

END


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 在前文中,我說過本系列文章的受眾是在現代前端體系下能夠熟練編寫業務代碼的同學,因此本文在介紹 webpack 配置時,僅提及構建一個庫所特有的配置,其餘配置請參考 webpack 官方文檔。 輸出產物 構建一個庫與構建一個一般應用最大的不同點在於 構建完成後輸出的產物 。 一般應用構建完成後會 ...
  • 參考 https://www.w3cschool.cn/css/css-boxmodel.html https://blog.csdn.net/zengyonglan/article/details/53301146 CSS 盒子模型(Box Model) 盒子模型 ("box model")是CS ...
  • "聊聊分散式事務,再說說解決方案" "分散式事務CAP理解論證 解決方案" "分散式系統的2PC、3PC詳細分析" "github tcc示例" "分散式事務、重覆消費、順序消費" 一、理論 CAP相關: CAP與BASE相關: "我的博客" 而對於分散式中的問題的解決方案,CAP原則出現,描述如下 ...
  • 職責鏈模式 定義 使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係。將這個對象連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個對象處理它為止。 UML圖 好處 當客戶提交一個請求時,請求是沿鏈傳遞直至有一個 ConcreteHandler 對象負責處理它 接收者和發送者都沒有對方 ...
  • 舉個慄子 路邊攤吃燒烤,老闆只有一人,又要記住點單,又要忙著收費,客戶做什麼都要排隊。。。 反例思路1 如果客戶多了,請求多,就容易混亂,他叫五串羊肉串,你叫三個烤雞翅,老闆哪裡忙得過來。代碼中的體現就是一句一句輸出,所以應該要有個服務員。。。 門面改造 Talk is cheap, show me ...
  • 舉個慄子 在M和N兩種品牌的手機裡面各做通訊錄和游戲兩種軟體。前提:M和N手機之間不通用。 反例思路1 以手機品牌為主體,各自實現軟體。 反例思路2 以手機軟體為主體,各自適配品牌。 弊端說明 對象的繼承關係是在編譯時就定義好了,所以無法在運行時改變從父類繼承的實現。 子類的實現與它的父類有非常緊密 ...
  • 想象一下你馬上出發要去一家餐廳吃飯,但是你去之前不確定會不會滿桌,你又不想排號。這時的你會有兩個選擇,如果你是個樂觀的人,內心戲可能會是「管他的,去了再說,大不了沒座就回來」。反之,如果你是一個悲觀的人,可能會先打個電話預約一下,先確認下肯定有座,同時交點定金讓餐廳預留好這個座位,這樣就可以直接去了 ...
  • 背景 薩班斯(SOX)法案。在美國上市公司必須遵循的“薩班斯(SOX)法案” 中要求對企業內部網路信息系統進行評估,其中涉及對業務系統操作、資料庫訪問等業務行為的審計。 日誌審計模型系統架構參考四層模型日誌審計類別1) HTTP 會話審計從流量中還原 HTTP 會話數據,並根據會話特征進一步深度解析... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...