• yii第一个应用blog


    1. 连接到数据库 

    大多数 Web 应用由数据库驱动,我们的测试应用也不例外。要使用数据库,我们首先需要告诉应用如何连接它。修改应用的配置文件 WebRoot/testdrive/protected/config/main.php 即可,如下所示:

    return array(
        ......
        'components'=>array(
            ......
            'db'=>array(
                'connectionString'=>'sqlite:protected/data/source.db',
            ),
        ),
        ......
    );

    上面的代码告诉 Yii 应用在需要时将连接到 SQLite 数据库。

    如何使用mysql:

    Tip: If you want to use MySQL instead of SQLite to store data, you may cre-
    ate a MySQL database named blog using the SQL statements in /wwwroot/yii/
    demos/blog/protected/data/schema.mysql.sql. Then, modify the application
    con guration as follows,
    return array(
    ......
    'components'=>array(
    ......
    'db'=>array(
    'connectionString' => 'mysql:host=localhost;dbname=blog',
    'emulatePrepare' => true,
    'username' => 'root',
    'password' => '',
    'charset' => 'utf8',
    'tablePrefix' => 'tbl ',
    ),
    ),
    ......
    );

    2. 实现 CRUD 操作 

    激动人心的时刻来了。我们想要为刚才建立的 tbl_user 表实现 CRUD (create, read, update 和 delete) 操作,这也是实际应用中最常见的操作。我们无需麻烦地编写实际代码,这里我们将使用 Gii —— 一个强大的基于Web 的代码生成器。 

    配置Gii

    为了使用 Gii,首先需要编辑文件 WebRoot/testdrive/protected/main.php,这是已知的 应用配置 文件:

    return array(
        ......
        'import'=>array(
            'application.models.*',
            'application.components.*',
        ),
     
        'modules'=>array(
            'gii'=>array(
                'class'=>'system.gii.GiiModule',
                'password'=>'pick up a password here',
            ),
        ),
    );

    然后,访问 URL http://hostname/testdrive/index.php?r=gii。这里我们需要输入密码,它是在我们在上面的配置中指定的。

    生成 User 模型

    登陆后,点击链接 Model Generator。它将显示下面的模型生成页面,

    在 Table Name 输入框中,输入 tbl_user。在 Model Class 输入框中,输入 User。然后点击 Preview 按钮。这里将展示将要生成的新文件。现在点击 Generate 按钮。一个名为 User.php 将生成到protected/models 目录中。如我们稍后描述的, User 模型类允许我们以面向对象的方式来访问数据表tbl_user 。

    Repeat the same procedure for the rest of the database tables, including tbl post, tbl
    comment, tbl tag and tbl lookup.

    Tip: We can also enter an asterisk character * in the Table Name Field. This will
    generate a model class for every database table in a single shot.

    3. Implementing CRUD Operations
    After the model classes are created, we can use the Crud Generator to generate the code
    implementing the CRUD operations for these models. We will do this for the Post and
    Comment models.

    On the Crud Generator page, enter Post (the name of the post model class we just created)
    in the Model Class eld, and then press the Preview button. We will see a lot more les
    will be generated. Press the Generate button to generate them.
    Repeat the same procedure for the Comment model.

    Let's take a look at the fi les generated by the CRUD generator. All the fi les are generated
    under /wwwroot/blog/protected. For convenience, we group them into controller fi les and
    view fi les:

    Testing
     We can test the features implemented by the code we just generated by accessing the
    following URLs:
    http://www.example.com/blog/index.php?r=post
    http://www.example.com/blog/index.php?r=comment

     主要post和comment,由于代码是自动生成的,所以2者还没有关联起来。

    Authenticating User
      Our blog application needs to differentiate between the system owner and guest users.
    Therefore, we need to implement the user authentication feature.As you may have found that the skeleton application already provides user authentication by checking if the username and password are both demo or admin. In this section, we
    will modify the corresponding code so that the authentication is done against the User  database table.

    User authentication is performed in a class implementing the [IUserIdentity] interface.
    The skeleton application uses the UserIdentity class for this purpose. The class is stored
    in the file /wwwroot/blog/protected/components/UserIdentity.php.

    Tip: By convention, the name of a class file must be the same as the corresponding
    class name suffixed with the extension .php. Following this convention, one can
    refer to a class using a path alias. For example, we can refer to the UserIdentity
    class with the alias application.components.UserIdentity. Many APIs in Yii
    can recognize path aliases (e.g. Yii::createComponent()), and using path aliases
    avoids the necessity of embedding absolute file paths in the code. The existence of
    the latter often causes trouble when we deploy an application.

    默认的代码如下:

    class UserIdentity extends CUserIdentity
    {
        /**
         * Authenticates a user.
         * The example implementation makes sure if the username and password
         * are both 'demo'.
         * In practical applications, this should be changed to authenticate
         * against some persistent user identity storage (e.g. database).
         * @return boolean whether authentication succeeds.
         */
        public function authenticate()
        {
            $users=array(
                // username => password
                'demo'=>'demo',
                'admin'=>'admin',
            );
            if(!isset($users[$this->username]))
                $this->errorCode=self::ERROR_USERNAME_INVALID;
            elseif($users[$this->username]!==$this->password)
                $this->errorCode=self::ERROR_PASSWORD_INVALID;
            else
                $this->errorCode=self::ERROR_NONE;
            return !$this->errorCode;
        }
    }

    我们将 UserIdentity 类做如下修改,

    <?php
    class UserIdentity extends CUserIdentity
    {
        private $_id;
     
        public function authenticate()
        {
            $username=strtolower($this->username);
            $user=User::model()->find('LOWER(username)=?',array($username));
            if($user===null)
                $this->errorCode=self::ERROR_USERNAME_INVALID;
            else if(!$user->validatePassword($this->password))
                $this->errorCode=self::ERROR_PASSWORD_INVALID;
            else
            {
                $this->_id=$user->id;
                $this->username=$user->username;
                $this->errorCode=self::ERROR_NONE;
            }
            return $this->errorCode==self::ERROR_NONE;
        }
     
        public function getId()
        {
            return $this->_id;
        }
    }

    在 authenticate() 方法中,我们使用 User 类来查询 tbl_user 表中 username 列值(不区分大小写)和提供的用户名一致的一行,请记住 User 类是在前面的章节中通过 gii 工具创建的。由于 User 类继承自CActiveRecord ,我们可以利用 ActiveRecord 功能 以 OOP 的风格访问 tbl_user 表。

    为了检查用户是否输入了一个有效的密码,我们调用了 User 类的 validatePassword 方法。我们需要按下面的代码修改 /wwwroot/blog/protected/models/User.php 文件。注意,我们在数据库中存储了密码的加密串和随机生成的SALT密钥,而不是存储明文密码。 所以当要验证用户输入的密码时,我们应该和加密结果做对比。

    class User extends CActiveRecord
    {
        ......
        public function validatePassword($password)
        {
            return $this->hashPassword($password,$this->salt)===$this->password;
        }
     
        public function hashPassword($password,$salt)
        {
            return md5($salt.$password);
        }
    }

    在 UserIdentity 类中,我们还覆盖(Override,又称为重写)了 getId() 方法,它会返回在 User 表中找到的用户的 id。父类 (CUserIdentity) 则会返回用户名。username 和 id 属性都将存储在用户 SESSION 中,可在代码的任何部分通过 Yii::app()->user 访问。

    提示: 在 UserIdentity 类中,我们没有显式包含(include)相应的类文件就访问了 CUserIdentity 类,这是因为 CUserIdentity 是一个由Yii框架提供的核心类。Yii 将会在任何核心类被首次使用时自动包含类文件。

    我们也对 User 类做了同样的事情。这是因为 User 类文件被放在了/wwwroot/blog/protected/models 目录,此目录已经通过应用配置中的如下几行代码被添加到了 PHP 的 include_path 中:

    return array(
        ......
        'import'=>array(
            'application.models.*',
            'application.components.*',
        ),
        ......
    );

    上面的配置说明,位于 /wwwroot/blog/protected/models 或/wwwroot/blog/protected/components 目录中的任何类将在第一次使用时被自动包含。

    UserIdentity 类主要用于 LoginForm 类中,它基于用户名和从登录页中收到的密码来实现用户验证。下面的代码展示了 UserIdentity 的使用:

    $identity=new UserIdentity($username,$password);
    $identity->authenticate();
    switch($identity->errorCode)
    {
        case UserIdentity::ERROR_NONE:
            Yii::app()->user->login($identity);
            break;
        ......
    }

    信息: 人们经常对 identity 和 user 应用组件感到困惑,前者代表的是一种验证方法,后者代表当前用户相关的信息。一个应用只能有一个 user 组件,但它可以有一个或多个 identity 类,这取决于它支持什么样的验证方法。一旦验证通过,identity 实例会把它自己的状态信息传递给 user 组件,这样它们就可以通过 user 实现全局可访问。

    model中rules() 方法中定义的规则会在模型实例调用其 validate() 或 save() 方法时逐一执行。注意: 请务必记住 rules() 中出现的属性必须是那些通过用户输入的属性。其他的属性,如 Post 模型中的 id 和 create_time ,是通过我们的代码或数据库设定的,不应该出现在 rules() 中。详情请参考 属性的安全赋值(Securing Attribute Assignments).

    自定义 relations() 方法 

    最后我们来自定义 relations() 方法,以指定与日志相关的对象。通过在 relations() 中声明这些相关对象,我们就可以利用强大的 Relational ActiveRecord (RAR) 功能来访问日志的相关对象,例如它的作者和评论。不需要自己写复杂的 SQL JOIN 语句。

    我们自定义 relations() 方法如下:

    public function relations()
    {
        return array(
            'author' => array(self::BELONGS_TO, 'User', 'author_id'),
            'comments' => array(self::HAS_MANY, 'Comment', 'post_id',
                'condition'=>'comments.status='.Comment::STATUS_APPROVED,
                'order'=>'comments.create_time DESC'),
            'commentCount' => array(self::STAT, 'Comment', 'post_id',
                'condition'=>'status='.Comment::STATUS_APPROVED),
        );
    }

    我们还在 Comment 模型类中定义了两个在上面的方法中用到的常量。

    class Comment extends CActiveRecord
    {
        const STATUS_PENDING=1;
        const STATUS_APPROVED=2;
        ......
    }

    relations() 中声明的关系表明:

    • 一篇日志属于一个作者,它的类是 User ,它们的关系建立在日志的 author_id 属性值之上;
    • 一篇日志有多个评论,它们的类是 Comment ,它们的关系建立在评论的 post_id 属性值之上。这些评论应该按它们的创建时间排列,且评论必须已通过审核;
    • commentCount 关系有一点特别,它返回一个关于日志有多少条评论的一个聚合结果。

    通过以上的关系声明,我们现在可以按下面的方式很容易的访问日志的作者和评论信息。

    $author=$post->author;
    echo $author->username;
     
    $comments=$post->comments;
    foreach($comments as $comment)
        echo $comment->content;

    关于如何声明和使用关系的更多详情,请参考 指南.

    class Post extends CActiveRecord
    {
        public function getUrl()
        {
            return Yii::app()->createUrl('post/view', array(
                'id'=>$this->id,
                'title'=>$this->title,
            ));
        }
    }

    注意我们除了使用日志的ID之外,还添加了日志的标题作为URL中的一个 GET 参数。这主要是为了搜索引擎优化 (SEO) 的目的,在 美化 URL 中将会讲述。

    由于 CComponent 是 Post 的最顶级父类,添加 getUrl() 这个 getter 方法使我们可以使用类似 $post->url 这样的表达式。当我们访问 $post->url 时,getter 方法将会被执行,它的返回结果会成为此表达式的值。关于这种组件的更多详情,请参考 指南

    postController中access rules:

    accessRules()

    我们将 /wwwroot/blog/protected/controllers/PostController.php 文件中的 accessRules() 方法修改如下:

    public function accessRules()
    {
        return array(
            array('allow',  // allow all users to perform 'list' and 'show' actions
                'actions'=>array('index', 'view'),
                'users'=>array('*'),
            ),
            array('allow', // allow authenticated users to perform any action
                'users'=>array('@'),
            ),
            array('deny',  // deny all users
                'users'=>array('*'),
            ),
        );
    }

    上面的规则说明:所有用户均可访问 index 和 view 动作,已通过身份验证的用户可以访问任意动作,包括admin 动作。在其他场景中,应禁止用户访问。注意这些规则将会按它们在此列出的顺序计算。第一条匹配当前场景的规则将决定访问权。例如,如果当前用户是系统所有者,他想尝试访问日志创建页,第二条规则将匹配成功并授予此用户权限。

    2. 自定义 创建 和 更新 操作 

    创建 和 更新 操作非常相似。他们都需要显示一个HTML表单用于收集用户的输入的信息,然后对其进行验证,然后将其存入数据库。主要的不同是 更新 操作需要把从数据库找到的已存在的日志数据重现在表单中。鉴于此,yiic 工具创建了一个局部视图 /wwwroot/blog/protected/views/post/_form.php ,它会插入 创建 和更新 视图来渲染所需的HTML表单

    我们先修改 _form.php 这个文件,使这个HTML表单只收集我们想要的输入:titlecontenttags 和status。我们使用文本域收集前三个属性的输入,还有一个下拉列表用来收集 status 的输入。此下拉列表的选项值就是可用的日志状态文本。

    <?php echo $form->dropDownList($model,'status',Lookup::items('PostStatus')); ?>

    在上面的代码中,我们调用了 Lookup::items('PostStatus') 以带回日志状态列表。

    然后我们修改 Post 类,使它可以在日志被存入数据库前自动设置几个属性 (例如 create_timeauthor_id)。我们覆盖 beforeSave() 方法如下:

    protected function beforeSave()
    {
        if(parent::beforeSave())
        {
            if($this->isNewRecord)
            {
                $this->create_time=$this->update_time=time();
                $this->author_id=Yii::app()->user->id;
            }
            else
                $this->update_time=time();
            return true;
        }
        else
            return false;
    }

    当我们保存日志时,我们想更新 tbl_tag 表以反映 Tag 的使用频率。我们可以在 afterSave() 方法中完成此工作,它会在日志被成功存入数据库后自动被Yii调用。

    protected function afterSave()
    {
        parent::afterSave();
        Tag::model()->updateFrequency($this->_oldTags, $this->tags);
    }
     
    private $_oldTags;
     
    protected function afterFind()
    {
        parent::afterFind();
        $this->_oldTags=$this->tags;
    }

    在这个实现中,因为我们想检测出用户在更新现有日志的时候是否修改了 Tag ,我们需要知道原来的 Tag 是什么, 鉴于此,我们还写了一个 afterFind() 方法把原有的 Tag 信息保存到变量 _oldTags 中。方法 afterFind()会在一个 AR 记录被数据库中的数据填充时自动被 Yii 调用。

    这里我们不再列出 Tag::updateFrequency() 方法的细节,读者可以参考/wwwroot/yii/demos/blog/protected/models/Tag.php 文件。

    控制器

     自定义 view 操作 

    view 操作是通过 PostController 中的 actionView() 方法实现的。它的显示是通过 view 视图文件/wwwroot/blog/protected/views/post/view.php 生成的。

    下面是在 PostController 中实现 view 操作的具体代码:

    public function actionView()
    {
        $post=$this->loadModel();
        $this->render('view',array(
            'model'=>$post,
        ));
    }
     
    private $_model;
     
    public function loadModel()
    {
        if($this->_model===null)
        {
            if(isset($_GET['id']))
            {
                if(Yii::app()->user->isGuest)
                    $condition='status='.Post::STATUS_PUBLISHED
                        .' OR status='.Post::STATUS_ARCHIVED;
                else
                    $condition='';
                $this->_model=Post::model()->findByPk($_GET['id'], $condition);
            }
            if($this->_model===null)
                throw new CHttpException(404,'The requested page does not exist.');
        }
        return $this->_model;
    }

    2. 自定义 index 操作 

    和 view 操作类似,我们在两处自定义 index 操作:PostController 中的 actionIndex() 方法和视图文件/wwwroot/blog/protected/views/post/index.php。我们主要需要添加对显示一个特定Tag下的日志列表的支持;

    下面就是在 PostController 中对 `actionIndex() 方法作出的修改:

    public function actionIndex()
    {
        $criteria=new CDbCriteria(array(
            'condition'=>'status='.Post::STATUS_PUBLISHED,
            'order'=>'update_time DESC',
            'with'=>'commentCount',
        ));
        if(isset($_GET['tag']))
            $criteria->addSearchCondition('tags',$_GET['tag']);
     
        $dataProvider=new CActiveDataProvider('Post', array(
            'pagination'=>array(
                'pageSize'=>5,
            ),
            'criteria'=>$criteria,
        ));
     
        $this->render('index',array(
            'dataProvider'=>$dataProvider,
        ));
    }

    在上面的代码中,我们首先为检索日志列表创建了一个查询标准(criteria),此标准规定只返回已发布的日志,且应该按其更新时间倒序排列。因为我们打算在显示日志列表的同时显示日志收到的评论数量,因此在这个标准中我们还指定了要带回 commentCount, 如果你还记得,它就是在 Post::relations() 中定义的一个关系。

    考虑到当用户想查看某个Tag下的日志列表时的情况,我们还要为指定的Tag添加一个搜索条件到上述标准中。

    使用这个查询标准,我们创建了一个数据提供者(data provider)。这主要出于三个目的。第一,它会在查询结果过多时实现数据分页。这里我们定义分页的页面大小为5。 第二,它会按用户的请求对数据排序。最后,它会填充排序并分页后的数据到小部件(widgets)或视图代码用于显示。

    完成 actionIndex() 后,我们将 index 视图修改为如下代码。 此修改主要是关于在用户指定显示Tag下的日志时添加一个 h1 标题。

    <?php if(!empty($_GET['tag'])): ?>
    <h1>Posts Tagged with <i><?php echo CHtml::encode($_GET['tag']); ?></i></h1>
    <?php endif; ?>
     
    <?php $this->widget('zii.widgets.CListView', array(
        'dataProvider'=>$dataProvider,
        'itemView'=>'_view',
        'template'=>"{items}
    {pager}",
    )); ?>

    注意上面的代码,我们使用了 CListView 来显示日志列表。这个小物件需要一个局部视图以显示每一篇日志的详情。这里我们制定了局部视图为 _view,也就是文件 /wwwroot/blog/protected/views/post/_view.php. 在这个视图脚本中,我们可以通过一个名为 $data 的本地变量访问显示的日志实例。

    参考资料

     

    日志管理 

     

    日志管理主要是在一个管理视图中列出日志,我们可以查看所有状态的日志,更新或删除它们。它们分别通过admin 操作和 delete 操作实现。yiic 生成的代码并不需要太多修改。下面我们主要解释这两个操作是怎样实现的。

    1. 在表格视图中列出日志 

    admin 操作在一个表格视图中列出了所有状态的日志。此视图支持排序和分页。下面就是 PostController 中的actionAdmin() 方法:

    public function actionAdmin()
    {
        $model=new Post('search');
        if(isset($_GET['Post']))
            $model->attributes=$_GET['Post'];
        $this->render('admin',array(
            'model'=>$model,
        ));
    }

    上面的代码由 yiic 工具生成,且未作任何修改。它首先创建了一个 search 场景(scenario) 下的 Post 模型。我们将使用此模型收集用户指定的搜索条件。然后我们把用户可能会提供的数据赋值给模型。 最后,我们以此模型显示 admin 视图。

    下面就是 admin 视图的代码:

    <?php
    $this->breadcrumbs=array(
        'Manage Posts',
    );
    ?>
    <h1>Manage Posts</h1>
     
    <?php $this->widget('zii.widgets.grid.CGridView', array(
        'dataProvider'=>$model->search(),
        'filter'=>$model,
        'columns'=>array(
            array(
                'name'=>'title',
                'type'=>'raw',
                'value'=>'CHtml::link(CHtml::encode($data->title), $data->url)'
            ),
            array(
                'name'=>'status',
                'value'=>'Lookup::item("PostStatus",$data->status)',
                'filter'=>Lookup::items('PostStatus'),
            ),
            array(
                'name'=>'create_time',
                'type'=>'datetime',
                'filter'=>false,
            ),
            array(
                'class'=>'CButtonColumn',
            ),
        ),
    )); ?>

    我们使用 CGridView 来显示这些日志。它允许我们在单页显示过多时可以分页并可以按某一列排序。我们的修改主要针对每一列的显示。例如,针对 title 列,我们指定它应该显示为一个超级链接,指向日志的详情页面。表达式$data->url 返回我们之前在 Post 类中定义的 url 属性值。

    提示: 当显示文本时,我们要调用 CHtml::encode() 对其中的HTML编码。这可以防止 跨站脚本攻击(cross-site scripting attack).

    2. 日志删除 

    在 admin 数据表格中,每行有一个删除按钮。点击此按钮将会删除相应的日志。在程序内部,这会触发如下实现的 delete 动作。

    public function actionDelete()
    {
        if(Yii::app()->request->isPostRequest)
        {
            // we only allow deletion via POST request
            $this->loadModel()->delete();
     
            if(!isset($_POST['ajax']))
                $this->redirect(array('index'));
        }
        else
            throw new CHttpException(400,'Invalid request. Please do not repeat this request again.');
    }

    上面的代码就是 yiic 生成的代码,未经任何修改。我们想在此对判断 $_POST['ajax'] 稍作解释。CGridView小物件有一个非常好的特性:它的排序、分页和删除操作默认是通过AJAX实现的。这就意味着在执行上述操作时,整个页面不会重新加载。然而,它也可以在非AJAX模式下运行(通过设置它的 ajaxUpdate 属性为 false 或在客户端禁用JavaScript)。delete 动作区分两个场景是必要的:如果删除请求通过AJAX提交,我们就不应该重定向用户的浏览器,反之则应该重定向。

    删除日志应该同时导致日志的所有评论被删除。额外的,我们应更新相关的删除日志后的 tbl_tag 表。 这两个任务都可以通过在 Post 模型类中写一个如下的 afterDelete 方法实现。

    protected function afterDelete()
    {
        parent::afterDelete();
        Comment::model()->deleteAll('post_id='.$this->id);
        Tag::model()->updateFrequency($this->tags, '');
    }

    上面的代码很直观:它首先删除了所有 post_id 和所删除的日志ID相同的那些评论。然后它针对所删日志中的tags 更新了 tbl_tag 表。

    提示: 由于 SQLite 并不真正支持外键约束,我们需要显式地删除属于所删日志的所有评论。在一个支持此约束的DBMS (例如 MySQL, PostgreSQL)中,可以设置好外键约束,这样如果删除了一篇日志,DBMS就可以自动删除其评论。这样的话,我们就不需要在我们的代码中显式执行删除了。

     
     
    Comment评论:

    自定义存储的流程 

    由于我们想要记录评论创建的时间,和我们在 Post 模型中的做法一样,我们覆盖 Comment 的 beforeSave()方法如下:

    protected function beforeSave()
    {
        if(parent::beforeSave())
        {
            if($this->isNewRecord)
                $this->create_time=time();
            return true;
        }
        else
            return false;
    }
  • 相关阅读:
    GORM模型(Model)创建
    GORM高级查询
    LogAgent
    安装kafka
    go读取日志tailf
    bubble清单
    go操作kafka
    GORM模型删除
    Vue 父子组件间的传值
    列表和表格
  • 原文地址:https://www.cnblogs.com/youxin/p/3526252.html
Copyright © 2020-2023  润新知