CodeIgniter源碼分析 4 - 加載器

當我們load模型交播,類庫等后就可以使用它們了,但是它們到底是怎么載入的践付?CI框架的自動載入是如何實現的秦士?它是如何處理類庫的擴展的?帶著這些疑問荔仁,這節(jié)我們看下加載器的源碼伍宦。

現代php框架一般使用composer配合psr-0/psr-4實現類庫自動載入的,例如laravel框架乏梁,而CI框架并沒有采用現代php框架流行的載入機制次洼,它自己實現了一套載入機制通過內置的加載器實現對資源的加載。

加載器能夠對類庫遇骑,模型卖毁,視圖等進行加載;CI框架對于資源加載的處理邏輯位于CI_ Loader.php類中落萎;

一般情況下亥啦,我們都是通過如下的方式加載資源

  $this->load->library('library');
  $this->load->model('model);
  //...


CI_Controller中Loader對象的初始化

加載資源都是通過CI_Loader對象的引用load進行加載的,那么就有個疑問

既然Loader對象可以加載別的資源练链,那么誰去加載Loader對象呢翔脱?答案是控制器對象。因為你必須先有Loader對象才能去加載別的資源媒鼓, 所以Loader對象應該在系統(tǒng)初始化的時候就加載完畢届吁,我們知道系統(tǒng)初始化是在引導文件CodeIgniter.php中完成的,而引導文件加載了Controller.php對象绿鸣,加載器就是在控制器對象中被加載并實例化的疚沐。

控制器對象中對存儲在is_loaded()對象數組中的類庫進行遍歷并作為它的屬性,這也是為什么我們在使用過程可以直接訪問類庫$this->input潮模,$this->config等原因亮蛔。

 public function __construct()
 {
        self::$instance =& $this;

        foreach (is_loaded() as $var => $class)
        {
            $this->$var =& load_class($class);
        }

        $this->load =& load_class('Loader', 'core');
        $this->load->initialize();   // 加載器初始化
        log_message('info', 'Controller Class Initialized');
    }

現在進入Loader對象所在的Loder.php去看下它是如何初始化的。


Loader對象

初始化

進入Loader.php中觀察其構造方法擎厢。我們看到 ob_get_level()這個函數究流,它返回了緩沖機制的嵌套級別。那么什么是緩沖機制的嵌套級別锉矢?

你在你的代碼中用了幾次ob_start()梯嗽,用一次就認為是一次嵌套;有時你沒有使用ob_start()卻發(fā)現ob_get_level()返回的是1沽损,那是因為在腳本的前文使用了ob_start()灯节,如果依賴ob_get_level()處理相應的邏輯,那么最好使用ob_end_clean()清一下緩存區(qū)绵估。

public function __construct()
    {
        $this->_ci_ob_level = ob_get_level();
        //is_loaded存儲了我們加載并實例過的對象
        $this->_ci_classes =& is_loaded();

        log_message('info', 'Loader Class Initialized');
    }

我們在CI_Conroller中看到加載器通過$this->load->initialize()做了初始化炎疆,其實這個方法完全可以放在加載器的構造方法中去。

public function initialize()
{
    $this->_ci_autoloader();
}

那么初始化的時候調用_ci_autoloader()這個方法是要做什么国裳?

自動載入

我們知道CI框架中要加載資源我們可以通過加載器$this->load->library()的方式手工加載形入,但是如果一個類中需要多次加載同一資源怎么辦? 于是缝左,CI框架就提供了一個自動載入的機制亿遂,在Appliaction/config/autoload.php中可以設置我們希望自動加載的資源浓若,這樣就省去了多次手工加載的麻煩。
當然蛇数,CI框架也支持composer自動載入挪钓,你只需要將composer生成的vendor/目錄扔到Appliaction/目錄下就可以了。

那么_ci_autoloader()就是用來處理自動載入的耳舅。

protected function _ci_autoloader()
    {
        //------------------------------------加載自動載入的配置文件autoload.php---------------------------------

        //不管有沒有多環(huán)境碌上,自動載入的配置文件autoload.php是一定要加載的
        if (file_exists(APPPATH.'config/autoload.php'))
        {
            include(APPPATH.'config/autoload.php');
        }

        //加載當前環(huán)境的配置文件
        if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/autoload.php'))
        {
            include(APPPATH.'config/'.ENVIRONMENT.'/autoload.php');
        }

        if ( ! isset($autoload))
        {
            return;
        }
  
      //---------------------------------讀取$autoload------------------------------------------------------

        //一般情況下資源也就是liarary,model浦徊,help這幾種馏予,并且這幾種資源的目錄已經被規(guī)定死了,
        //但萬一你不想把資源歸到這幾個目錄下盔性,那么你可以在$autoload['packages'] = array()中設置你要加載資源的的目錄
        if (isset($autoload['packages']))
        {
            foreach ($autoload['packages'] as $package_path)
            {
                $this->add_package_path($package_path);
            }
        }

        //讀取需要自動加載的相關配置文件
        if (count($autoload['config']) > 0)
        {
            foreach ($autoload['config'] as $val)
            {
                $this->config($val);
            }
        }

        //讀物需要自動加載的幫助函數和語言包
        foreach (array('helper', 'language') as $type)
        {
            if (isset($autoload[$type]) && count($autoload[$type]) > 0)
            {
                $this->$type($autoload[$type]);
            }
        }

        //讀取需要自動載入的驅動
        if (isset($autoload['drivers']))
        {
            foreach ($autoload['drivers'] as $item)
            {
                $this->driver($item);
            }
        }

        // 讀取需要自動載入的類庫霞丧,注意:這里對數據庫的載入是做了區(qū)分的,數據庫的連接應該只有一次
        if (isset($autoload['libraries']) && count($autoload['libraries']) > 0)
        {
            // Load the database driver.
            if (in_array('database', $autoload['libraries']))
            {
                $this->database();
                $autoload['libraries'] = array_diff($autoload['libraries'], array('database'));
            }

            foreach ($autoload['libraries'] as $item)
            {
                $this->library($item);
            }
        }

        //讀物需要自動載入的模型
        if (isset($autoload['model']))
        {
            $this->model($autoload['model']);
        }
    }

通過分析可以看到冕香,自動載入其本質還是通過library()蚯妇,model()等方法替我們做了手動載入資源的事。

接下來我們看看具體加載類庫library()暂筝,模型model()箩言,視圖view()等的細節(jié)。


類庫加載

類庫加載是通過library()方法實現的焕襟,加載類庫時我們不僅可以單個加載陨收,還可以批量加載,同時還支持為類庫取別名鸵赖,現在我們看下該方法的細節(jié)處理务漩。

public function library($library, $params = NULL, $object_name = NULL)
   {

       //首先判斷單個加載還是批量加載,如果是批量加載就遞歸自身它褪,
       if (empty($library))
       {
           return $this;
       }
       elseif (is_array($library))
       {
           foreach ($library as $key => $value)
           {
               if (is_int($key))
               {
                   $this->library($value, $params);
               }
               else
               {
                   $this->library($key, $params, $value);
               }
           }

           return $this;
       }

       //參數只支持數組格式
       if ($params !== NULL && ! is_array($params))
       {
           $params = NULL;
       }

       //調用_ci_load_library加載類庫
       $this->_ci_load_library($library, $params, $object_name);
       return $this;
   }

對于類庫的加載我們發(fā)現最終其實調用的是_ci_load_library()這個方法饵骨。

載入機制

拿到類庫名后,解析出對應的路徑和類名茫打,然后從約定好的類庫目錄(APPPATH/library和BASEPATH/library)下去加載居触,一旦加載成功后調用相應的函數做實例化。

protected function _ci_load_library($class, $params = NULL, $object_name = NULL)
    {
        //------------------------------解析出路徑和類名---------------------------------

        //先對傳入的類庫名做一個處理老赤,去掉擴展名和兩端的路徑分割符
        $class = str_replace('.php', '', trim($class, '/'));


        //處理類庫位于二級目錄的情況轮洋,解析到類庫所在的目錄和類名
        if (($last_slash = strrpos($class, '/')) !== FALSE)
        {
            // Extract the path
            $subdir = substr($class, 0, ++$last_slash);

            // Get the filename from the path
            $class = substr($class, $last_slash);
        }
        else
        {
            $subdir = '';
        }

        $class = ucfirst($class);

        //----------------------------------------查找系統(tǒng)內置類庫------------------------------------------------------

        //加載內置類庫并做實例化
        if (file_exists(BASEPATH.'libraries/'.$subdir.$class.'.php'))
        {
            return $this->_ci_load_stock_library($class, $subdir, $params, $object_name);
        }

        //------------------------------------查找用戶自定義的類庫------------------------------------------------

        //接著從約定好的類庫目錄中去查找我們需要的類,數組_ci_library_paths定義了我們需要查找的目錄抬旺,
        //$_ci_library_paths =  array(APPPATH, BASEPATH);
        foreach ($this->_ci_library_paths as $path)
        {

            if ($path === BASEPATH)
            {
                continue;
            }

            $filepath = $path.'libraries/'.$subdir.$class.'.php';

            //為了防止多次加載同一類庫弊予,先判斷一下是不是在前文已經加載過了,
            if (class_exists($class, FALSE))
            {
                //如果已經加載了類庫开财,但是還沒有掛載到全局對象$CI(CI_Controller)的話汉柒,
                //調用_ci_init_library()實例化該類庫并掛載到全局對象$CI下
                if ($object_name !== NULL)
                {
                    $CI =& get_instance();
                    if ( ! isset($CI->$object_name))
                    {
                        return $this->_ci_init_library($class, '', $params, $object_name);
                    }
                }

                log_message('debug', $class.' class already loaded. Second attempt ignored.');
                return;
            }
            //沒找到的話再次循環(huán)去下一個目錄查找
            elseif ( ! file_exists($filepath))
            {
                continue;
            }

            //和前面_ci_init_library()一樣误褪,實例化該類庫并掛載到全局對象$CI下
            include_once($filepath);
            return $this->_ci_init_library($class, '', $params, $object_name);
        }

        // 如果還沒找到的話,假設該類庫位于和它同名的一個子目錄中碾褂,再次嘗試加載
        if ($subdir === '')
        {
            return $this->_ci_load_library($class.'/'.$class, $params, $object_name);
        }

        // If we got this far we were unable to find the requested class.
        log_message('error', 'Unable to load the requested class: '.$class);
        show_error('Unable to load the requested class: '.$class);
    }

在_ci_load_library()方法中解析出類和路徑后振坚,接下來就是實例化類庫了。

由于加載的類庫可能是我們自己定義的類庫或者系統(tǒng)內置的類庫斋扰,所以在加載成功做實例化的時候做了區(qū)分,分別從_ci_load_stock_library()和_ci_init_library()中實例化內置類庫和我們自定義的類庫啃洋。

先看從_ci_load_stock_library()方法中加載內置類庫传货。

注意:由于系統(tǒng)類庫我們也可以擴展,所在實例化系統(tǒng)內置類庫時會有類名$prefix的判斷宏娄,系統(tǒng)類庫的$prefix是以CI開頭的问裕,而我們擴展的系統(tǒng)類庫的$prefix是在config.php中subclass_prefix上指定的。

protected function _ci_load_stock_library($library_name, $file_path, $params, $object_name)
    {
        $prefix = 'CI_';

        //---------------------------------------獲取擴展類庫的前綴配置---------------------------------------------------

        //判斷系統(tǒng)核心類是不是被擴展了孵坚,注意:擴展類繼承了系統(tǒng)核心類的所有功能
        if (class_exists($prefix.$library_name, FALSE))
        {
            if (class_exists(config_item('subclass_prefix').$library_name, FALSE))
            {
                $prefix = config_item('subclass_prefix');
            }

            //如果該類已經被加載但是還沒被掛載到全局對象$CI上的話粮宛,調用_ci_init_library()進行實例化并掛載
            if ($object_name !== NULL)
            {
                $CI =& get_instance();
                if ( ! isset($CI->$object_name))
                {
                    return $this->_ci_init_library($library_name, $prefix, $params, $object_name);
                }
            }

            log_message('debug', $library_name.' class already loaded. Second attempt ignored.');
            return;
        }

        //---------------------接下來又是拿到要查找的目錄,不厭其煩的去跑到這幾個目錄中去加載---------------------------------------------

        $paths = $this->_ci_library_paths;
        array_pop($paths);
        array_pop($paths);
        array_unshift($paths, APPPATH);

        //跑到APPPATH/libraries目錄下去加載系統(tǒng)內置類庫
        foreach ($paths as $path)
        {
            if (file_exists($path = $path.'libraries/'.$file_path.$library_name.'.php'))
            {
                // Override
                include_once($path);
                if (class_exists($prefix.$library_name, FALSE))
                {
                    return $this->_ci_init_library($library_name, $prefix, $params, $object_name);
                }
                else
                {
                    log_message('debug', $path.' exists, but does not declare '.$prefix.$library_name);
                }
            }
        }

        //跑到BASEPATH/libraries目錄下去加載系統(tǒng)內置類庫

        include_once(BASEPATH.'libraries/'.$file_path.$library_name.'.php');


        //跑到APPPATH/libraries目錄下去加載被擴展了的系統(tǒng)內置類庫
        $subclass = config_item('subclass_prefix').$library_name;
        foreach ($paths as $path)
        {
            if (file_exists($path = $path.'libraries/'.$file_path.$subclass.'.php'))
            {
                include_once($path);
                if (class_exists($subclass, FALSE))
                {
                    $prefix = config_item('subclass_prefix');
                    break;
                }
                else
                {
                    log_message('debug', APPPATH.'libraries/'.$file_path.$subclass.'.php exists, but does not declare '.$subclass);
                }
            }
        }

        //找到后調用_ci_init_library()進行實例化并掛載
        return $this->_ci_init_library($library_name, $prefix, $params, $object_name);
    }

在上面的源碼分析中卖宠,我們看到多次_ci_init_library()方法巍杈,我們也多次說了該方法是實例化類庫并掛載到全局對象$CI(CI_Controller)上。

類庫實例化并掛載到全局對象$CI(CI_Controller)上

對于類庫實例化的參數有必要說明一下扛伍,CI框架支持你在創(chuàng)建一個類庫時在 APPPATH/config/ 目錄下創(chuàng)建一個和類名相同的配置文件筷畦,所以接下來你會看見在實例化類庫的時候,如果沒有為類庫傳入參數刺洒,就會嘗試去讀該類的配置文件的內容作為實例化的參數鳖宾。

現在看下_ci_init_library()方法的源碼。

 protected function _ci_init_library($class, $prefix, $config = FALSE, $object_name = NULL)
    {

        //-------------------------------------讀取類庫的配置問價---------------------------------------

        //如果該類庫沒有傳入參數逆航,那么嘗試從/config/目錄下去加載一個和當前類庫名一樣的配置文件作為實例化時的參數鼎文。
        if ($config === NULL)
        {
            //$config_component是Config對象的引用,在_ci_get_component()你會看到是從$CI上獲取了Config對象
            $config_component = $this->_ci_get_component('config');

            //接下來就是考慮盡可能多的情況因俐,從而require相關的配置文件
            if (is_array($config_component->_config_paths))
            {
                $found = FALSE;
                foreach ($config_component->_config_paths as $path)
                {
                    if (file_exists($path.'config/'.strtolower($class).'.php'))
                    {
                        include($path.'config/'.strtolower($class).'.php');
                        $found = TRUE;
                    }
                    elseif (file_exists($path.'config/'.ucfirst(strtolower($class)).'.php'))
                    {
                        include($path.'config/'.ucfirst(strtolower($class)).'.php');
                        $found = TRUE;
                    }

                    if (file_exists($path.'config/'.ENVIRONMENT.'/'.strtolower($class).'.php'))
                    {
                        include($path.'config/'.ENVIRONMENT.'/'.strtolower($class).'.php');
                        $found = TRUE;
                    }
                    elseif (file_exists($path.'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php'))
                    {
                        include($path.'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php');
                        $found = TRUE;
                    }

                    //找到的話就沒必要再繼續(xù)去require了
                    if ($found === TRUE)
                    {
                        break;
                    }
                }
            }
        }

        //-------------------------------------------類庫的實例化與掛載---------------------------------------

        $class_name = $prefix.$class;

        //為了保險起見再次判斷一下該類庫是否已經加載
        if ( ! class_exists($class_name, FALSE))
        {
            log_message('error', 'Non-existent class: '.$class_name);
            show_error('Non-existent class: '.$class_name);
        }

        if (empty($object_name))
        {
            $object_name = strtolower($class);
            if (isset($this->_ci_varmap[$object_name]))
            {
                $object_name = $this->_ci_varmap[$object_name];
            }
        }

        //如果你設置的類庫別名已經存在的話拇惋,就說明有沖突了
        $CI =& get_instance();
        if (isset($CI->$object_name))
        {
            if ($CI->$object_name instanceof $class_name)
            {
                log_message('debug', $class_name." has already been instantiated as '".$object_name."'. Second attempt aborted.");
                return;
            }

            show_error("Resource '".$object_name."' already exists and is not a ".$class_name." instance.");
        }

        //我們在構造方法中看到了_ci_classes是對is_loaded()的引用,當我們每實例化一個類后抹剩,需要在is_loaded()緩存一下
        $this->_ci_classes[$object_name] = $class;

        //最后實例化該類庫并掛載到全局對象$CI上
        $CI->$object_name = isset($config)
            ? new $class_name($config)
            : new $class_name();
    }

類庫加載的源碼分析就到此蚤假,可以看出類庫的加載其實就是解析出類名后從定義好的目錄中去加載,順便在初始化的時候把需要自動載入的類庫也給調用library()方法也給加載進來吧兔,大概的流程圖就是這樣

類庫加載

接下來看下模型加載的源碼磷仰。

模型加載

模型的加載和類庫的加載大同小異,也是解析出對應的模型名稱境蔼,然后跑到指定的模型所在的目錄去加載它灶平,加載成功后一樣做實例化并掛載到全局對象$CI上伺通,同時也緩存它。

public function model($model, $name = '', $db_conn = FALSE)
    {
        //是不是批量加載多個模型逢享,是的話遞歸處理
        if (empty($model))
        {
            return $this;
        }
        elseif (is_array($model))
        {
            foreach ($model as $key => $value)
            {
                is_int($key) ? $this->model($value, '', $db_conn) : $this->model($key, $value, $db_conn);
            }

            return $this;
        }


        //解析模型名罐监,拿到類名,順便判斷下是不是有子目錄

        $path = '';

        if (($last_slash = strrpos($model, '/')) !== FALSE)
        {
            $path = substr($model, 0, ++$last_slash);
            $model = substr($model, $last_slash);
        }

        if (empty($name))
        {
            $name = $model;
        }

        //_ci_models可看成是對已加載模型的緩存的瞒爬,如果發(fā)現該模型已經加載過弓柱,直接返回
        if (in_array($name, $this->_ci_models, TRUE))
        {
            return $this;
        }

        //判斷模型的別名是不是和其他對象的引用有沖突
        $CI =& get_instance();
        if (isset($CI->$name))
        {
            show_error('The model name you are loading is the name of a resource that is already being used: '.$name);
        }

        //加載模型的時候還可以支持動態(tài)切換數據庫連接
        if ($db_conn !== FALSE && ! class_exists('CI_DB', FALSE))
        {
            if ($db_conn === TRUE)
            {
                $db_conn = '';
            }

            $this->database($db_conn, FALSE, TRUE);
        }

        //因為所有模型都是CI_Model的子類,所以先要確保CI_Model被加載進來了
        if ( ! class_exists('CI_Model', FALSE))
        {
            load_class('Model', 'core');
        }

        //對類名處理下侧但,然后跑到模型所在的目錄去加載
        $model = ucfirst(strtolower($model));

        foreach ($this->_ci_model_paths as $mod_path)
        {
            if ( ! file_exists($mod_path.'models/'.$path.$model.'.php'))
            {
                continue;
            }

            //找到了的話矢空,實例化后在_ci_models中緩存,并掛載到全局對象$CI上去
            require_once($mod_path.'models/'.$path.$model.'.php');

            $this->_ci_models[] = $name;
            $CI->$name = new $model();
            return $this;
        }

        // couldn't find the model
        show_error('Unable to locate the model you have specified: '.$model);
    }

關于加載器的源碼分析就到此結束了禀横,可以看到加載器對于資源的加載就是做了三件事屁药。

  • 解析類名和路徑。
  • 跑到對應的目錄去加載柏锄。
  • 一旦找到就實例化并掛載到全局對象$CI(CI_controller)上酿箭。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市趾娃,隨后出現的幾起案子缭嫡,更是在濱河造成了極大的恐慌,老刑警劉巖抬闷,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件械巡,死亡現場離奇詭異,居然都是意外死亡饶氏,警方通過查閱死者的電腦和手機讥耗,發(fā)現死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疹启,“玉大人古程,你說我怎么就攤上這事『把拢” “怎么了挣磨?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長荤懂。 經常有香客問我茁裙,道長,這世上最難降的妖魔是什么节仿? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任晤锥,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘矾瘾。我一直安慰自己女轿,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布壕翩。 她就那樣靜靜地躺著蛉迹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪放妈。 梳的紋絲不亂的頭發(fā)上北救,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音芜抒,去河邊找鬼珍策。 笑死,一個胖子當著我的面吹牛挽绩,可吹牛的內容都是我干的。 我是一名探鬼主播驾中,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼唉堪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了肩民?” 一聲冷哼從身側響起唠亚,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎持痰,沒想到半個月后灶搜,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡工窍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年割卖,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片患雏。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡鹏溯,死狀恐怖,靈堂內的尸體忽然破棺而出淹仑,到底是詐尸還是另有隱情丙挽,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布匀借,位于F島的核電站颜阐,受9級特大地震影響,放射性物質發(fā)生泄漏吓肋。R本人自食惡果不足惜凳怨,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望是鬼。 院中可真熱鬧猿棉,春花似錦磅叛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至杖爽,卻和暖如春敲董,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背慰安。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工腋寨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人化焕。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓萄窜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親撒桨。 傳聞我的和親對象是個殘疾皇子查刻,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內容