置顶 博客
概念 把一个大型的单个应用程序和服务拆分为数个甚至数十个的支持微服务,它可扩展单个组件而不是整个的应用程序堆栈,从而满足服务等级协议。 传统的开发模式就是把所有功能都放在一个包里,基本不存在依赖,这样的优势在于开发简单,集中式管理,功能都在本地,不存在分布式的管理和调度消耗。但缺点也很明显:效率低,开发都在同一个项目改代码,相互等待,冲突不断。稳定性差,一个微小的问题,都可能导致整个应用挂掉。另外在资源利用上表现出明显的劣势,比如电商双11大促场景,下单压力非常大,评价的压力相对较少,那么我们希望临时增配应对双11的大流程,只能全部增配,而不能定点只对订单服务增配。所以微服务的架构开始慢慢流行并应用于大型的网站平台。 那么引入今天的主题,Yii 如何做微服务?Yii 可以轻松使用,而不需要基本和高级模板中包含的功能。换句话说,Yii 已经是一个微框架。不需要由模板提供的目录结构与 Yii 一起工作。 安装 Yii 为您的项目创建一个目录并将工作目录更改为该路径。示例中使用的命令是基于 Unix 的,但在 Windows 中也存在类似的命令。 mkdir micro-app cd micro-app Note:需要一些 Composer 的知识才能继续。如果您还不知道如何使用 composer,请花些时间阅读 Composer 指南。 使用您最喜爱的编辑器在 micro-app 目录下创建 composer.json 文件并添加以下内容: { "require": { "yiisoft/yii2": "~2.0.0" }, "repositories": [ { "type": "composer", "url": "https://asset-packagist.org" } ] } 保存文件并运行 composer install 命令。这将安装框架及其所有依赖项。 创建项目结构 安装框架之后,需要为此应用程序创建一个入口点。入口点是您尝试打开应用程序时将执行的第一个文件。出于安全原因,建议将入口点文件放在一个单独的目录中,并将其设置为Web根目录。 创建一个 web 目录并将 index.php 放入其中,内容如下: <?php // comment out the following two lines when deployed to production defined('YII_DEBUG') or define('YII_DEBUG', true); defined('YII_ENV') or define('YII_ENV', 'dev'); require(__DIR__ . '/../vendor/autoload.php'); require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); $config = require __DIR__ . '/../config.php'; (new yii\web\Application($config))->run(); 还要创建一个名为 config.php 的文件,它将包含所有的应用程序配置: <?php return [ 'id' => 'micro-app', //设置`micro-app`的根目录 'basePath' => __DIR__, // 控制器所在目录。 'controllerNamespace' => 'micro\controllers', // 设置命名空间为 micro 'aliases' => [ '@micro' => __DIR__, ], //默认访问地址 'defaultRoute' => 'home/index', 'components' => [ //请求配置 'request' => [ 'cookieValidationKey' => 'test&123456', 'parsers' => [ 'application/json' => 'yii\web\JsonParser', ] ], //Url 美化 'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, 'enableStrictParsing' => false, 'rules' => [ '<controller:\w+>/<action:\w+>/<id:\w+>' => '<controller>/<action>', ], ], //数据库配置 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=micro', 'username' => 'root', 'password' => '数据库密码', 'charset' => 'utf8', ], ], ]; Info:尽管配置可以保存在 index.php 文件中,建议单独使用它。 这样它也可以用于控制台应用程序,如下所示。 您的项目现在已经准备进行编码了。尽管由您决定项目目录结构,只要您遵守命名空间即可。 创建第一个控制器 在创建控制器之前,创建一个 controllers/base 目录并创建一个基础控制器 BaseController。 <?php namespace micro\controllers\base; use yii\web\Controller; class BaseController extends Controller { //关闭 csrf 验证 public $enableCsrfValidation = false; } 然后在 controller 文件夹下面 新建一个 SiteController.php,这是默认的 控制器将处理没有路径信息的请求。 <?php namespace micro\controllers; use yii\web\Controller; class HomeController extends BaseController { public function actionIndex() { return '欢迎来到 Yii2.0 微服务!'; } } 如果您想为此控制器使用不同的名称,则可以配置 yii\base\Application::$defaultRoute 进行更改。 例如,对于 HomeController 将会是 'defaultRoute' => 'home/index'。 在这一点上,项目结构应该如下所示: micro-app/ ├── composer.json ├── config.php ├── web/ └── index.php └── controllers/ └── base └── BaseController.php └── HomeController.php └── vendor 如果您尚未设置 Web 服务器,则可能需要查看Web服务器配置文件示例。 另一种选择是使用 yii serve 命令,它将使用 PHP 内置 web 服务器。 您可以通过以下方式从 micro-app / 目录运行它: vendor/bin/yii serve --docroot=./web 在浏览器中打开应用程序URL现在应该打印出“欢迎来到 Yii2.0 微服务!”,它已经在 HomeController::actionIndex() 中返回。 Info:在我们的示例中,我们已将默认应用程序名称空间 app 更改为 micro, 以表明您不受此名称的限制(如果您是这样认为), 然后调整 controllers namespace 并设置正确的别名。
精华贴
10天前 喜欢(1) 浏览(200) 评论(0)
博客
YII2可以在命令行执行php命令,作为半路出家的撩妹君可谓是抠脚福音。作为一个屌丝级的程序员必须要有智能提示代码的IDE,比如PHPstorm。至于如何免费使用嘛。。。。。 首先明白YII2自带的command示列 \app\commands\HelloController class HelloController extends Controller { /** * This command echoes what you have entered as the message. * @param string $message the message to be echoed. * @return int Exit code */ public function actionIndex($message = 'hello world') { echo $message . "\n"; return ExitCode::OK; } } 首先CD到工程的根目录,目录下有一个 yii文件,我们执行yii hello/index控制台打印了hello world 因此我们可以知道 命令格式如下 yii 控制器名称/动作名称。actionIndex($message = 'hello world')有一个参数,并且有一个默认值,很多时候我们也需要这样的操作。所有我们执行yii hello/index helloYii 将会打印出 helloYii。因此我们可以得知 命令格式如下 yii 控制器名称/动作名称 参数1 参数2... 而action方法中 actionName($parameter1,$parameter...)。如果需要操作数据库则需要在config/console.php配置db因为web上的配置和command是不同的。YII2有了这个后我们可以实现快速插入测试数据,快速执行一些不明白的函数,快速的对自己写的代码进行测试,如果需要配置定时任务给服务器运行也是可以的。如果是Linux系统的话得给yii文件执行权限 并且命令格式如下./yii 控制器名称/动作名称。 很多人也喜欢写到一个web的action,然后用来请求,但是web的action是执行完毕才能看见结果而command里面是可以实时echo出结果的,所有一些小动作都可以写一个command,比如说请求一个api一万次,并且每次都显示出结果来。这样有了command就能很方便的满足上面的需求了
24天前 喜欢(0) 浏览(195) 评论(3)
博客
我们在使用YII2的时候,经常会遇到需要定义config里各种参数,某些时候我们需要进行一些参数的修改。比如需要一个邮件发送功能,但是发送邮件的参数保存在数据库里面,所以我们就需要自定义发送邮件的各项参数了。 我们通常会进行以下配置 'mailer' => [ 'class' => 'yii\swiftmailer\Mailer', 'useFileTransport' => false, 'transport' => [ 'class' => 'Swift_SmtpTransport', 'host' => 'smtp.qq.com', 'username' => '888888@qq.com', 'password' => '8888', 'port' => '25', 'encryption' => 'tls', ], 'messageConfig'=>[ 'charset'=>'UTF-8', 'from'=>['88888@qq.com'=>'admin'] ], ], 获取到mailer对象如下,Yii::$app->mailer,YII2 的设计Yii::$app下的对象 刚好components里配置的对象, 通过上面的components的配置结构我们知道mailer对象是由yii\swiftmailer\Mailer类生成的,并且mailer还有一个transport对象由Swift_SmtpTransport类生产,通过以下操作 我们就得到了$mAiler实列: $mAiler->setTo($to);//收件人 $mAiler->setSubject($subject);//主题 $mAiler->setTextBody($text);//内容 $mAiler->send(); 我们也可以通过Yii::$app->mailer 拿到 mailer->对象,然后mailer->transport->host...等等进行变量的定义,但是 PHPstorm 没有对代码进行提示,所以不太喜欢,如果你不慌倒是可以使用。 如果需要临时改变以下db的属性你也可以有以下操作,至于效果嘛。。。我还没去试,估计是对当前一次的http请求生效吧
29天前 喜欢(0) 浏览(223) 评论(3)
博客
打开配置文件将下面代码添加到 components => [...]中(例:高级版默认配置在/common/config/main-local.php) 'mailer' => [ 'class' => 'yii\swiftmailer\Mailer', 'viewPath' => '@common/mail', 'useFileTransport' => false, //这里一定要改成false,不然邮件不会发送 'transport' => [ 'class' => 'Swift_SmtpTransport', 'host' => 'smtp.163.com', 'username' => 'xianan_huang@163.com', 'password' => '*********', //如果是163邮箱,此处要填授权码 'port' => '25', 'encryption' => 'tls', ], ], 在控制器中调用: $mail = \Yii::$app->mailer->compose() ->setFrom(['xianan_huang@163.com' => 'Yii 中文网']) ->setTo('391430388@qq.com') ->setSubject('邮件发送配置') //->setTextBody('Yii中文网教程真好 www.yii-china.com') //发布纯文字文本 ->setHtmlBody("<br>Yii中文网教程真好!www.yii-china.com") //发布可以带html标签的文本 ->send(); if($mail) echo 'success'; else echo 'fail'; 注意:很多报错原因都是因为163邮箱的smtp没有开,进入邮箱设置一下 默认不开启smtp要绑定手机之后才能开启
29天前 喜欢(2) 浏览(171) 评论(3)
博客
有些旧版的cms是asp.net做的,而我们现在需要把用到它使用的数据库,那么问题来了,目前yii2支持sql只到2008,如何使用mssql连接更早版本的sql呢,这时候我我们就需要对yii2的mssql里的schema进行修改,使他支持2000的sql。开始我们的教程吧: 1.首先找到yii2解析mssql的scheme: vendor/yiisoft/yii2/db/mssql/Schema.php 2.更改解析代码: 1> 找到findColumns 的函数 大概在232行左右 2>替代解析 代码如下 : /** * Collects the metadata of table columns. * @param TableSchema $table the table metadata * @return boolean whether the table exists in the database */ protected function findColumns($table) { $pdo = $this->db->getSlavePdo(); $version = explode('.', $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION)); //var_dump($version);exit; if (intval($version[0]<9)) { $sql = <<<SQL2000 SELECT column_name = a.name, is_identity = case when exists(SELECT 1 FROM sysobjects where xtype='PK' and parent_obj=a.id and name in ( SELECT name FROM sysindexes WHERE indid in( SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid))) then 1 else 0 end, data_type = case when b.name in ('ntext','datetime') then b.name when isnull(COLUMNPROPERTY(a.id,a.name,'Scale'),0)>0 then b.name + '(' + convert(varchar(20), COLUMNPROPERTY(a.id,a.name,'PRECISION')) + ','+convert(varchar(10), COLUMNPROPERTY(a.id,a.name,'Scale'))+')' else b.name + '(' + convert(varchar(20), COLUMNPROPERTY(a.id,a.name,'PRECISION')) + ')' end , is_nullable = case when a.isnullable=1 then 'Yes' else 'No' end, column_default = isnull(e.text,''), comment = isnull(g.[value],'') FROM syscolumns a left join systypes b on a.xusertype=b.xusertype inner join sysobjects d on a.id=d.id and d.xtype='U' and d.name<>'dtproperties' left join syscomments e on a.cdefault=e.id left join sysproperties g on a.id=g.id and a.colid=g.smallid left join sysproperties f on d.id=f.id and f.smallid=0 where d.name='{$table->fullName}' order by a.id,a.colorder SQL2000; } else { $columnsTableName = 'INFORMATION_SCHEMA.COLUMNS'; $whereSql = "[t1].[table_name] = '{$table->name}'"; if ($table->catalogName !== null) { $columnsTableName = "{$table->catalogName}.{$columnsTableName}"; $whereSql .= " AND [t1].[table_catalog] = '{$table->catalogName}'"; } if ($table->schemaName !== null) { $whereSql .= " AND [t1].[table_schema] = '{$table->schemaName}'"; } $columnsTableName = $this->quoteTableName($columnsTableName); $sql = <<<SQL SELECT [t1].[column_name], [t1].[is_nullable], [t1].[data_type], [t1].[column_default], COLUMNPROPERTY(OBJECT_ID([t1].[table_schema] + '.' + [t1].[table_name]), [t1].[column_name], 'IsIdentity') AS is_identity, CONVERT(VARCHAR, [t2].[value]) AS comment FROM {$columnsTableName} AS [t1] LEFT OUTER JOIN [sys].[extended_properties] AS [t2] ON [t2].[minor_id] = COLUMNPROPERTY(OBJECT_ID([t1].[TABLE_SCHEMA] + '.' + [t1].[TABLE_NAME]), [t1].[COLUMN_NAME], 'ColumnID') AND OBJECT_NAME([t2].[major_id]) = [t1].[table_name] AND [t2].[class] = 1 AND [t2].[class_desc] = 'OBJECT_OR_COLUMN' AND [t2].[name] = 'MS_Description' WHERE {$whereSql} SQL; } try { $columns = $this->db->createCommand($sql)->queryAll(); if (empty($columns)) { return false; } } catch (\Exception $e) { return false; } foreach ($columns as $column) { $column = $this->loadColumnSchema($column); foreach ($table->primaryKey as $primaryKey) { if (strcasecmp($column->name, $primaryKey) === 0) { $column->isPrimaryKey = true; break; } } if ($column->isPrimaryKey && $column->autoIncrement) { $table->sequenceName = ''; } $table->columns[$column->name] = $column; } return true; } 3.分析这段代码: 1>获取数据库的版本: $pdo = $this->db->getSlavePdo(); $version = explode('.', $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION)); 2>根据版本解析: if (intval($version[0]<9)) { }else{} 3> 解析sql2000: $sql = <<<SQL2000 SELECT column_name = a.name, is_identity = case when exists(SELECT 1 FROM sysobjects where xtype='PK' and parent_obj=a.id and name in ( SELECT name FROM sysindexes WHERE indid in( SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid))) then 1 else 0 end, data_type = case when b.name in ('ntext','datetime') then b.name when isnull(COLUMNPROPERTY(a.id,a.name,'Scale'),0)>0 then b.name + '(' + convert(varchar(20), COLUMNPROPERTY(a.id,a.name,'PRECISION')) + ','+convert(varchar(10), COLUMNPROPERTY(a.id,a.name,'Scale'))+')' else b.name + '(' + convert(varchar(20), COLUMNPROPERTY(a.id,a.name,'PRECISION')) + ')' end , is_nullable = case when a.isnullable=1 then 'Yes' else 'No' end, column_default = isnull(e.text,''), comment = isnull(g.[value],'') FROM syscolumns a left join systypes b on a.xusertype=b.xusertype inner join sysobjects d on a.id=d.id and d.xtype='U' and d.name<>'dtproperties' left join syscomments e on a.cdefault=e.id left join sysproperties g on a.id=g.id and a.colid=g.smallid left join sysproperties f on d.id=f.id and f.smallid=0 where d.name='{$table->fullName}' order by a.id,a.colorder SQL2000; 4> else 就是原来的解析方式 3.打开sql的数据库scheme的缓存,在db.php文件中添加 :'enableSchemaCache'=>true 既可以 这样我们就能使用sql2000数据库了,快去试试吧
3天前 喜欢(0) 浏览(37) 评论(2)
博客
required : 必须值验证属性||CRequiredValidator 的别名, 确保了特性不为空. [['字段名1','字段名2'],required] //字段1 2 必填 [['字段名'],required,'requiredValue'=>'必填值','message'=>'提示信息']; email : 邮箱验证||CEmailValidator 的别名,确保了特性的值是一个有效的电邮地址. ['email', 'email']; match : 正则验证||CRegularExpressionValidator 的别名, 确保了特性匹配一个正则表达式. [['字段名'],'match','pattern'=>'正则表达式','message'=>'提示信息']; [['字段名'],'match','not'=>ture,'pattern'=>'正则表达式','message'=>'提示信息']; /*正则取反*/ url : 网址||CUrlValidator 的别名, 确保了特性是一个有效的路径. ['website', 'url', 'defaultScheme' => 'http']; captcha(验证码)||CCaptchaValidator 的别名,确保了特性的值等于 CAPTCHA 显示出来的验证码. ['verificationCode', 'captcha']; safe : 安全 ['description', 'safe']; compare :(比较) CCompareValidator 的别名, 确保了特性的值等于另一个特性或常量. ['repassword', 'compare', 'compareAttribute' => 'password','message'=>'两次输入的密码不一致!'], //compareValue:比较常量值 operator:比较操作符 ['age', 'compare', 'compareValue' => 30, 'operator' => '>=']; default : 默认值||CDefaultValueValidator 的别名, 为特性指派了一个默认值. ['age', 'default', 'value' => null]; exist : 存在||CExistValidator 的别名, 确保属性值存在于指定的数据表字段中. ['字段名', 'exist']; file : 文件||CFileValidator 的别名, 确保了特性包含了一个上传文件的名称. ['primaryImage', 'file', 'extensions' => ['png', 'jpg', 'gif'], 'maxSize' => 1024*1024*1024] filter : 滤镜||CFilterValidator 的别名, 使用一个filter转换属性. //'skipOnArray' => true 非必填 [['username', 'email'], 'filter', 'filter' => 'trim', 'skipOnArray' => true]; in : 范围||CRangeValidator 的别名, 确保了特性出现在一个预订的值列表里. ['level', 'in', 'range' => [1, 2, 3]]; unique : 唯一性||CUniqueValidator 的别名, 确保了特性在数据表字段中是唯一的. ['字段名', 'unique'] 补充:联合唯一索引rule规则 [ ['app_id', 'group_id'], 'unique', 'targetAttribute' => ['app_id', 'group_id'], 'message' => 'app_id和group_id已经被占用!' ], integer : 整数 ['age', 'integer']; number : 数字 ['salary', 'number']; double : 双精度浮点型 ['salary', 'double']; date : (日期) [['from', 'to'], 'date']; string : 字符串 ['username', 'string', 'length' => [4, 24]]; boolean : 是否为一个布尔值||CBooleanValidator 的别名 ['字段名', 'boolean', 'trueValue' => true, 'falseValue' => false, 'strict' => true]; image :是否为有效的图片文件 [ 'primaryImage', 'image', 'extensions' => 'png, jpg', 'minWidth' => 100, 'maxWidth' => 1000, 'minHeight' => 100, 'maxHeight' => 1000 ] each:遍历,ids 和 product_ids 是数字的集合 [['ids', 'product_ids'], 'each', 'rule' => ['integer']], 自定义rules: ['password', 'validatePassword'], /** * Validates the password. * This method serves as the inline validation for password. * * @param string $attribute the attribute currently being validated * @param array $params the additional name-value pairs given in the rule */ public function validatePassword($attribute, $params) { if (!$this->hasErrors()) { $user = $this->getUser(); if (!$user || !$user->validatePassword($this->password)) { $this->addError($attribute, '账号或者密码错误!'); } } }
29天前 喜欢(3) 浏览(185) 评论(2)
博客
基础 Gridview 插件被应用于数据呈现,它提供了很多功能,如浏览、排序、分页和数据过滤。 下面是一个简单的 Gridview 应用实例: <?= GridView::widget([ 'dataProvider' => $dataProvider, 'columns' => [ 'id', 'name', 'created_at:datetime', // ... ], ]) ?> 用法示例 1.在例头添加排序,在cloumns中加入以下代码 ['class' => 'yii\grid\SerialColumn'] 2.列表勾选框,在cloumns中加入以下代码 ['class' => 'yii\grid\CheckboxColumn'], 3.列数据快速格式化:时间戳转化为时间格式显示 'created_at:datetime', 4.显示关联表数据:前提是在model中有关联关系,例如:getAuthor() 'author.name', //获取关联表author的name的值 5.列表中显示图片:显示一张50*100的图片,label_img为图片地址 'label_img'=>[ 'label' => '标签图', 'format' => [ 'image', [ 'height' =>50, 'width' => 100 ] ], 'value' => function($model){ return $model->label_img; } ], 6.显示状态,且带过滤 [ 'attribute' => 'is_valid', 'label' => '发布状态', 'value' => function($model) { return $model->is_valid == 0 ? '未发布' : '发布'; }, 'filter' => [ 0 => '未发布', 1 => '发布' ] ], 7.显示带html标签的例值:正常情况下是过滤html标签的 [ 'attribute' => 'content', 'format' => 'raw', 'value' => function ($model) { return $model->content; }, ], 8.自定义按钮:{view} {update} {delete} 为默认,可以不填显示默认,也可以覆盖重新定义 [ 'class' => 'yii\grid\ActionColumn', 'template' => '{test} {view} {update} {delete}', 'header' => '操作', 'buttons' => [ 'test' => function ($url, $model, $key) { return Html::a('测试按钮', $url, ['data-method' => 'post','data-pjax'=>'0'] ); }, 'delete'=> function ($url, $model, $key){ return Html::a('删除', ['delete', 'id'=>$model->id],[ 'data-method'=>'post', //POST传值 'data-confirm' => '确定删除该项?', //添加确认框 ] ) ; } ], ], 9.修改列表顶部分页信息 //{begin}:当前列的第一个元素序号 //{end}:当前页的最后一个元素序号 //{count}:当前页的元素总数 //{totalCount}:所有元素总数 //{page}:当前页 //{pageCount}:总页数 <?= GridView::widget([ 'dataProvider' => $dataProvider, 'summary' => '第{begin}-{end}页,共计{totalCount}篇文章', ... 以上为gridview的一些基本操作,当然还有很多其他情景,但要活学活用,举一反三
1月前 喜欢(3) 浏览(233) 评论(2)
博客
通过一个例子简单说一下Yii2 模型中的场景(scenario)应用,现在在 文章表里面有 title image content 三个的字段,当我创建一个文章 的时候,我想三个字段全部是必填项,但是你修改的时候,title content 两个字段是必填的, iamge 可以不填写。正常的情况下, [['title', 'content', 'image'], 'required',], 但是我们更改的时候 只需要 [['title', 'content'], 'required'], 就可以了,但是少了 image 字段,我们的表单就无法提交,这种问题怎么办啊?? 场景可以帮你解决这种问题,下面是一个简单的场景实例。 1.首先我们在 model 里面定义一下场景 类名必须是 scenarios() public function scenarios() { return [ 'create' => ['title', 'image', 'content'], 'update' => ['title', 'content'], ]; } 2.好的,如上所示,场景的基本设置我们就已经完成一部分了,下面我们设置 rules() ,调用场景我们用 on 关键字 [['title', 'content'], 'required', 'on' => ['create', 'update']], [['image'], 'required', 'on' => 'create'], [ ['image'], 'image', 'enableClientValidation' => true, 'maxSize' => 1024, 'message' => '您上传的文件过大', 'on' => ['create', 'update'] ], on 指定的就是场景,一个场景用字符串,多个场景用数组 3.好的,model 里面我们就设置完毕了 现在开始调用吧 Controller里面 $model = $this->findModel($id); $model->setScenario('update'); //或者 $model->scenario = 'update'; 都可以 上面的意思就是 调用 update 场景。
1天前 喜欢(0) 浏览(19) 评论(0)
博客
在处理一个文章内容时,需要在指定标签后面插入 指定内容 1.查找到指定位置(例如 我要在第三个标签<\p>后插入广告代码): /** * 选择到标签指定位置后 字节长度 * $find 标签名字 * $number 第几个标签位置 * $body 被查找的内容 * @param unknown $number * @param unknown $find * @param unknown $body * @return number * @author kangjy */ public function position($number,$find,$body) { //获取标签长度 $findLen = strlen($find); //初始化位置 $position =0; for ($number=1; $number<=3; $number++) { $position = stripos($body, $find, $position); //var_dump('第'.$number.'个 位置'.$position); $position += $findLen; // var_dump('第'.$number.'个 位置加寻找词的长度'); } return $position ; } 2.插入指定字符串代码 /** * 指定位置插入字符串 * @param $str 原字符串 * @param $i 插入位置 * @param $substr 插入字符串 * @return string 处理后的字符串 * @author kangjy */ public function insertToStr($str, $i,$substr){ //指定插入位置前的字符串 $startstr=""; for($j=0; $j<$i; $j++){ $startstr .= $str[$j]; } //指定插入位置后的字符串 $laststr=""; for ($j=$i; $j<strlen($str); $j++){ $laststr .= $str[$j]; } //将插入位置前,要插入的,插入位置后三个字符串拼接起来 $str = $startstr . $substr . $laststr; //返回结果 return $str; } 这样就可以了
2天前 喜欢(0) 浏览(24) 评论(0)
博客
如果做API的话,如何使别人再调用你的接口时能够有一个统一标准的json或者jsonp格式,然而 json响应的格式和内容,每个人的约定都是有差异的,所以我们必须再数据出去之前做一定的处理。 1.首先我们需要初始化去调用beforeSend,因为我们需要对beforesend做一些处理,以下是init初始化处理代码: /** * (non-PHPdoc) * @see \yii\base\Object::init() */ public function init() { parent::init(); //绑定beforeSend事件,更改数据输出格式 Yii::$app->getResponse()->on(Response::EVENT_BEFORE_SEND, [$this, 'beforeSend']); } 2.然后我们就需要对beforesend进行处理,处理点有下面几个重点: 1>更改数据输出格式 2>默认情况下输出Json数据 3>如果客户端请求时有传递$_GET['callback']参数,输出Jsonp格式 4>请求正确时数据为 {"success":true,"data":{...}} 5>请求错误时数据为 {"success":false,"data":{"name":"Not Found","message":"页面未找到。","code":0,"status":404}} 6>具体代码如下: /** * 更改数据输出格式 * 默认情况下输出Json数据 * 如果客户端请求时有传递$_GET['callback']参数,输入Jsonp格式 * 请求正确时数据为 {"success":true,"data":{...}} * 请求错误时数据为 {"success":false,"data":{"name":"Not Found","message":"页面未找到。","code":0,"status":404}} * @param \yii\base\Event $event */ public function beforeSend($event) { /* @var $response \yii\web\Response */ $response = $event->sender; $isSuccessful = $response->isSuccessful; if ($response->statusCode>=400) { //异常处理 if (true && $exception = Yii::$app->getErrorHandler()->exception) { $response->data = $this->convertExceptionToArray($exception); } //Model出错了 if ($response->statusCode==422) { $messages=[]; foreach ($response->data as $v) { $messages[] = $v['message']; } //请求错误时数据为 {"success":false,"data":{"name":"Not Found","message":"页面未找到。","code":0,"status":404}} $response->data = [ 'name'=> 'valide error', 'message'=> implode(" ", $messages), 'info'=>$response->data ]; } $response->statusCode = 200; } elseif ($response->statusCode>=300) { $response->statusCode = 200; $response->data = $this->convertExceptionToArray(new ForbiddenHttpException(Yii::t('yii', 'Login Required'))); } //请求正确时数据为 {"success":true,"data":{...}} $response->data = [ 'success' => $isSuccessful, 'data' => $response->data, ]; $response->format = Response::FORMAT_JSON; \Yii::$app->getResponse()->getHeaders()->set('Access-Control-Allow-Origin', '*'); \Yii::$app->getResponse()->getHeaders()->set('Access-Control-Allow-Credentials', 'true'); //jsonp 格式输出 if (isset($_GET['callback'])) { $response->format = Response::FORMAT_JSONP; $response->data = [ 'callback' => $_GET['callback'], 'data'=>$response->data, ]; } } 3.针对请求可能会发生一些异常,同样我们也需要对异常进行一些标准化处理,将异常转换为array输出,具体代码如下: /** * 将异常转换为array输出 * @see \yii\web\ErrorHandle * @param \Exception $exception * @return multitype:string NULL Ambigous <string, \yii\base\string> \yii\web\integer \yii\db\array multitype:string NULL Ambigous <string, \yii\base\string> \yii\web\integer \yii\db\array */ protected function convertExceptionToArray($exception) { if (!YII_DEBUG && !$exception instanceof UserException && !$exception instanceof HttpException) { $exception = new HttpException(500, Yii::t('yii', 'An internal server error occurred.')); } $array = [ 'name' => ($exception instanceof Exception || $exception instanceof ErrorException) ? $exception->getName() : 'Exception', 'message' => $exception->getMessage(), 'code' => $exception->getCode(), ]; if ($exception instanceof HttpException) { $array['status'] = $exception->statusCode; } if (YII_DEBUG) { $array['type'] = get_class($exception); if (!$exception instanceof UserException) { $array['file'] = $exception->getFile(); $array['line'] = $exception->getLine(); $array['stack-trace'] = explode("\n", $exception->getTraceAsString()); if ($exception instanceof \yii\db\Exception) { $array['error-info'] = $exception->errorInfo; } } } if (($prev = $exception->getPrevious()) !== null) { $array['previous'] = $this->convertExceptionToArray($prev); } return $array; } 好了,这样我们就有了标准同一个的api接口返回数据格式了,在调用接口的人员也不用为了格式不统一感到烦恼
3天前 喜欢(1) 浏览(30) 评论(0)
博客
yii2是一个快速开发的框架,其中gii扩展不得不说是一个很大的助力,通过gii自动生成代码,把一些通用的代码交给程序去生成,很大程度上减少开发者的时间成本。但gii也有一些弊端,那就是生成的代码是yii2自带的模板,每次生成的代码并不是我们想要的,因而每次都要去对应的做调整。 那么如何才能让程序生成我们想要的代码呢?没错,yii2的gii扩展是支持自定义模板的,通过自定义模板我们就可以让gii生成我们想要的代码,又一次节约了去修改生成模板的时间成本。 配置 gii生成器所用到的模板文件位于目录 vendor\yiisoft\yii2-gii\generators\crud\default,我们既然要自定义模板,最好是在原有的模板基础下做调整。 1.复制一份模板,拷贝default目录,放在任意位置,此处我们放置在根目录 /backend/giitpl/crud 中。 2.修改模板(本文为教程不做细说,此处自行修改即可) 3.打开配置文件 /backend/config/main-local.php 修改$config['modules']['gii']的配置(如下): $config['modules']['gii'] = [ 'class' => 'yii\gii\Module', 'allowedIPs' => ['127.0.0.1', '::1'], 'generators' => [ 'crud' => [ //生成器名称 'class' => 'yii\gii\generators\crud\Generator', 'templates' => [ //设置我们自己的模板 //模板名 => 模板路径 'myCrud' => '@backend/giitpl/crud/default', ] ] ], ]; 4.通过gii生成代码,打开gii界面,使用crud generator生成代码(注:此处配置要修改code template模板) 选择我们自定义的模板,然后点击生成,那么gii自定义的模板代码就生成好了。 总结 通过自定义gii模板,我们能够更加灵活快速的完成需要的功能,把许多相似度高,应用场景多,功能类似(比如:列表页,详情页等)的代码做成gii的模板,通过程序去生成。还是那句话yii2就是一个快速开发的框架。
7天前 喜欢(0) 浏览(57) 评论(0)
博客
Gii 这个扩展无疑是 yii2 快速开发的一大助力,通过使用gii生成代码很大程序上节约了开发的时间成本,那么如何使用gii这个组件呢?本文为你们简单介绍一下yii2中gii的一些常用功能。 生成模型(Model) 在数据库新建一张test的测试表 CREATE TABLE `test` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID', `name` varchar(255) DEFAULT NULL COMMENT '名称', `desc` varchar(255) DEFAULT NULL COMMENT '描述', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 1.打开gii的界面 /index.php?r=gii或者 /gii(url美化之后) 2.创建Model 首先,点击Model generator下面的start按钮,进入model生成界面,输入数据表名test,输入model class(一般会自动生成一个,也可以自定义model class名称) 然后,点击下方的preview,会列出要生成的模型 最后,点击generate生成model文件,如下提示测生成成功 生成CRUD 同样,点击CRUD Generator,去创建CRUD也就是增删改查的操作界面及功能,输入对应的参数(注:view path 不填,即生成在默认的位置) 然后,点击preview,查看要生成的文件列表(注:如果已经存在对应的控制会显示下方diff,可以选择覆盖或者保留) 最后点击Generate生成相应的CRUD文件,然后就可以查看对应的页面了,如此简单就完成了一个数据表的增删改成
11天前 喜欢(0) 浏览(93) 评论(0)
博客
package main import ( "context" "fmt" "go.etcd.io/etcd/clientv3" "time" ) func main() { var ( config clientv3.Config client *clientv3.Client lease clientv3.Lease leaseResp *clientv3.LeaseGrantResponse leaseId clientv3.LeaseID leaseRespChan <-chan *clientv3.LeaseKeepAliveResponse err error ) //客户端配置 config = clientv3.Config{ Endpoints: []string{"127.0.0.1:2379"}, DialTimeout: 5 * time.Second, } //建立连接 if client, err = clientv3.New(config); err != nil { fmt.Println(err) return } //上锁(创建租约,自动续租) lease = clientv3.NewLease(client) //设置一个ctx取消自动续租 ctx,cancleFunc := context.WithCancel(context.TODO()) //设置10秒租约(过期时间) if leaseResp,err = lease.Grant(context.TODO(),10);err != nil { fmt.Println(err) return } //拿到租约id leaseId = leaseResp.ID //自动续租(不停地往管道中扔租约信息) if leaseRespChan,err =lease.KeepAlive(ctx,leaseId);err != nil { fmt.Println(err) } //启动一个协程去监听 go listenLeaseChan(leaseRespChan) //业务处理 kv := clientv3.NewKV(client) //创建事务 txn := kv.Txn(context.TODO()) txn.If(clientv3.Compare(clientv3.CreateRevision("/cron/lock/job9"),"=",0)). Then(clientv3.OpPut("/cron/lock/job9","xxx",clientv3.WithLease(leaseId))). Else(clientv3.OpGet("/cron/lock/job9"))//否则抢锁失败 //提交事务 if txtResp,err :=txn.Commit();err != nil { fmt.Println(err) return } else { //判断是否抢锁 if !txtResp.Succeeded { fmt.Println("锁被占用:",string(txtResp.Responses[0].GetResponseRange().Kvs[0].Value)) return } } fmt.Println("处理任务") //释放锁(停止续租,终止租约) defer cancleFunc()//函数退出取消自动续租 defer lease.Revoke(context.TODO(),leaseId) //终止租约(去掉过期时间) time.Sleep(10 * time.Second) } func listenLeaseChan(leaseRespChan <-chan *clientv3.LeaseKeepAliveResponse) { var ( leaseKeepResp *clientv3.LeaseKeepAliveResponse ) for { select { case leaseKeepResp = <-leaseRespChan: if leaseKeepResp == nil { fmt.Println("租约失效了") goto END } else { fmt.Println(leaseKeepResp.ID) } } } END: }
23天前 喜欢(0) 浏览(132) 评论(0)
博客
少比比直接代码(你可以理解为给key设置过期时间,但是比redis要强大的是它可以自动续租) package main import ( "context" "fmt" "go.etcd.io/etcd/clientv3" "go.etcd.io/etcd/mvcc/mvccpb" "time" ) func main() { var ( config clientv3.Config client *clientv3.Client err error kv clientv3.KV keepResp *clientv3.LeaseKeepAliveResponse keepRespChan <-chan *clientv3.LeaseKeepAliveResponse ) //创建租约 lease := clientv3.NewLease(client) //设置10秒租约(过期时间为10秒) if leaseRes,err := lease.Grant(context.TODO(),10);err != nil { fmt.Println(err) return } else { //得到租约id leaseId := leaseRes.ID //定义一个上下文使得租约5秒过期 ctx,_:= context.WithTimeout(context.TODO(),5*time.Second) //自动续租(底层会每次讲租约信息扔到 <-chan *clientv3.LeaseKeepAliveResponse 这个管道中) if keepRespChan,err = lease.KeepAlive(ctx,leaseId);err != nil { fmt.Println(err) return } //启动一个新的协程来select这个管道 go func() { for { select { case keepResp = <- keepRespChan: if keepResp == nil { fmt.Println("租约失效了") goto END//失效跳出循环 } else { //每秒收到一次应答 fmt.Println("收到租约应答",keepResp.ID) } } } END: }() //得到操作键值对的kv kv = clientv3.NewKV(client) //进行写操作 if putResp,err = kv.Put(context.TODO(),"/cron/lock/job1","",clientv3.WithLease(leaseId)/*高速etcd这个key对应的租约*/);err != nil { fmt.Println(err) return } else { fmt.Println("写入成功",putResp.Header.Revision/*这东西你可以理解为每次操作的id*/) } } //监听这个key的租约是否过期 for { if getResp,err = kv.Get(context.TODO(),"/cron/lock/job1");err != nil { fmt.Println(err) return } if getResp.Count == 0 { fmt.Println("kv过期了") break } fmt.Println("kv没过期",getResp.Kvs) time.Sleep(2 * time.Second) }
23天前 喜欢(0) 浏览(130) 评论(0)
博客
etcd做注册服务使用 类似java生态的zookeeper,最近在学学习 1、下载etcd包 //直接下载即可(因为包比较大有翻墙,可以直接去 https://golangtc.com/download/package 下载) go get go.etcd.io/etcd/clientv3 2、使用记录 packge main import ( "context" "fmt" "go.etcd.io/etcd/clientv3" //"log/syslog" "time" ) func main() { var ( config clientv3.Config client *clientv3.Client err error kv clientv3.KV putResp *clientv3.PutResponse getResp *clientv3.GetResponse delResp *clientv3.DeleteResponse keepResp *clientv3.LeaseKeepAliveResponse keepRespChan <-chan *clientv3.LeaseKeepAliveResponse ) } //客户端配置 config = clientv3.Config{ Endpoints:[]string{"127.0.0.1:2379"}, DialTimeout:5 * time.Second, } //建立连接 if client,err = clientv3.New(config);err != nil { fmt.Println(err) return } //得到操作etcd键值对的kv kv = clientv3.NewKV(client) //写入etcd if putResp,err = kv.Put(context.TODO(),"/cron/jobs/job2","....",clientv3.WithPrevKV()/*可选参数,得到上次操作的值*/);err != nil { fmt.Println(err) } else { fmt.Println(putResp.Header.Revision) if putResp.PrevKv != nil { fmt.Println(string(putResp.PrevKv.Value)) } } //读取某个key的value值 getResp,err = kv.Get(context.TODO(),"/cron/jobs/job1"/*,clientv3.WithCountOnly()可选参数,得到数量*/) if err != nil { fmt.Println(err) return } else { fmt.Println(getResp.Kvs[0].Value/*得到的是一个切片*/) } //读取前缀为XXX的所有的key的value(需要加上参数clientv3.WithPrefix()) if getResp,err = kv.Get(context.TODO(),"/cron/jobs/",clientv3.WithPrefix());err != nil { fmt.Println(err) return } else { fmt.Println(getResp.Kvs) } //删除操作 if delResp,err = kv.Delete(context.TODO(),"/cron/jobs/job2",clientv3.WithPrevKV()/*得到删除之前的值*/);err != nil { fmt.Println(err) return } else { if len(delResp.PrevKvs) != 0 { fmt.Println(delResp.PrevKvs) } }
23天前 喜欢(0) 浏览(136) 评论(0)
博客
场景 现在有客户表、订单表、图书表、作者表 客户表:Customer【id customer_name】 订单表:Order【id order_name customer_id book_id】 图书表:Book【id book_name author_id】 作者表:Author【id author_name】 怎么来确定是一对多还是一对一呢?这个很简单,比如下面的Customer,一个Customer有多个Order,所以就是一对多;又比如Book,一个Book只有一个作者(这里的情况就是一本书只有一个作者),所以就是一对一。 模型定义 下面是这4个个模型的定义,只写出其中的关联 Customer class Customer extends \yii\db\ActiveRecord { //这是获取客户的订单,由上面我们知道这个是一对多的关联,一个客户有多个订单 public function getOrders() { //第一个参数为要关联的子表模型类名, //第二个参数指定 通过子表的customer_id,关联主表的id字段 return $this->hasMany(Order::className(), ['customer_id' =>'id']); } } Order class Order extends \yii\db\ActiveRecord { //获取订单所属用户 public function getCustomer() { //同样第一个参数指定关联的子表模型类名 return $this->hasOne(Customer::className(), ['id' =>'customer_id']); } //获取订单中所有图书 public function getBooks() { //同样第一个参数指定关联的子表模型类名 return $this->hasMany(Book::className(), ['id' =>'book_id']); } } Book class Book extends \yii\db\ActiveRecord { //获取图书的作者 public function getAuthor() { //同样第一个参数指定关联的子表模型类名 return $this->hasOne(Author::className(), ['id' => 'author_id']); } } Author class Autor extends \yii\db\ActiveRecord { } hasMany、hasOne使用 Yii2中的表之间的关联有2种,它们用来指定两个模型之间的关联。 一对多:hasMany 一对一:hasOne 返回结果:这两个方法的返回结果都为yii\db\ActiveQuery对象 - 第一个参数:所关联的模型的类名称。 - 第二个参数:是一个数组,其中键为所关联的模型中的属性,值为当前模型中的属性。 关联的使用 现在我们获取一个客户的所有的订单信息 // 获取一个客户信息 $customer = Customer::findOne(1); //通过在Customer中定义的关联方法(getOrders())来获取这个客户的所有的订单。 $orders = $customer->orders; 上面的两行代码会生成如下sql语句 SELECT * FROM customer WHERE id=1; SELECT * FROM order WHERE customer_id=1; 关联结果缓存 如果客户的订单改变了,我们再重新调用 $orders = $customer->orders; 再次得到订单的时候你会发现没有变化。原因是只会在第一次执行$customer->orders的时候才会去数据库里面查询,然后会把结果缓存起来,以后查询的时候都不会再执行sql。 那么如果我想再次执行sql如何做呢?可以执行 unset($customer->orders); $customer->orders; 然后就可以从数据库里面取数据了。 定义多个关联 同样,我们还可以在Customer里面定义多个关联。 如返回总数大于100的订单。 class Customer extends \yii\db\ActiveRecord { public function getBigOrders($threshold = 100) { return $this->hasMany(Order::className(), ['customer_id' => 'id']) ->where('subtotal > :threshold', [':threshold' => $threshold]) ->orderBy('id'); } } 关联的两种访问方式 如上面的,如果使用 $customer->bigOrders 将会得到大于100的所有的订单。如果要返回大于200的订单可以这样写 $orders = $customer->getBigOrders(200)->all(); 从上面可以看出访问一个关联的时候有两种方法 如果以函数的方式调用,会返回一个 ActiveQuery 对象($customer->getOrders()->all()) 如果以属性的方式调用,会直接返回模型的结果($customer->orders) with的使用看如下代码,是取一个客户的订单 // 执行sql语句: SELECT * FROM customer WHERE id=1 $customer = Customer::findOne(1); //执行sql:SELECT * FROM order WHERE customer_id=1 $orders1 = $customer->orders; //这个不会执行sql,直接使用上面的缓存结果 $orders2 = $customer->orders; 如果现在我们要取出100个用户,然后访问每个用户的订单,从上面的了解我们可能会写出如下代码 // 执行sql语句: SELECT * FROM customer LIMIT 100 $customers = Customer::find()->limit(100)->all(); foreach ($customers as $customer) { //执行sql: SELECT * FROM order WHERE customer_id=... $orders = $customer->orders; // 处理订单。。。 } 然而,如果真要这样写的话,会在foreach的每个循环里面都执行一次sql去数据库里面查询数据。因为每个$customer对象都是不一样的。 为了解决上面的问题 可以使用 yii\db\ActiveQuery::with()。 其中with的参数为关系的名称,也就在model里面定义的getOrders,getCustomer中的orders和customer // 先执行sql: SELECT * FROM customer LIMIT 100; // SELECT * FROM orders WHERE customer_id IN (1,2,...) $customers = Customer::find()->limit(100)->with('orders')->all(); foreach ($customers as $customer) { // 在这个循环的时候就不会再执行sql了 $orders = $customer->orders; // ...handle $orders... } 如果使用了select来指定返回的列,一定要确保返回的列里面包含所关联的模型的关联字段,否则将不会返回关联的表的Model $orders = Order::find()->select(['id', 'amount'])->with('customer')->all(); // $orders[0]->customer 的结果将会是null // 因为上面的select中没有返回所关联的模型(customer)中的指定的关联字段。 // 如果加上customer_id,$orders[0]->customer就可以返回正确的结果 $orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all(); 给with加过滤条件 查询一个客户大于100的订单 //首先执行sql: SELECT * FROM customer WHERE id=1 $customer = Customer::findOne(1); // 再执行查询订单的sql语句:SELECT * FROM order WHERE customer_id=1 AND subtotal>100 $orders = $customer->getOrders()->where('subtotal>100')->all(); 查询100个客户的,每个客户的总合大于100的订单 // 下面的代码会执行sql语句: // SELECT * FROM customer LIMIT 100 // SELECT * FROM order WHERE customer_id IN (1,2,...) AND subtotal>100 $customers = Customer::find()->limit(100)->with([ 'orders' => function($query) { $query->andWhere('subtotal>100'); }, ])->all(); 在这里with的参数为数组,键为关联的名称,值为回调函数。 也就是说 对 orders 这个关联返回的 ActiveQuery,再执行一次$query->andWhere(‘subtotal>100′); 使用joinWith进行表关联 我们都知道可以用join on来写多个表之间的关联。先看看yii2中joinWith的声明 joinWith( $with, $eagerLoading = true, $joinType = ‘LEFT JOIN’ ) $with 数据类型为字符串或数组, 如果为字符串,则为模型中定义的关联的名称(可以为子关联)。 如果为数组,键为model中以getXXX格式定义的关联,值为对这个关联的进一步的回调操作。 $eagerLoading 是否加载在 $with 中关联的模型的数据。 $joinType 联接类型,可用值为:LEFT JOIN、INNER JOIN,默认值为LEFT JOIN // 订单表和客户表以Left join的方式关联。 // 查找所有订单,并以客户 ID 和订单 ID 排序 $orders = Order::find()->joinWith('customer')->orderBy('customer.id, order.id')->all(); // 订单表和客户表以Inner join的方式关联 // 查找所有的订单和书 $orders = Order::find()->innerJoinWith('books')->all(); // 使用inner join 连接order中的 books关联和customer关联。 // 并对custmer关联再次进行回调过滤:找出24小时内注册客户包含书籍的订单 $orders = Order::find()->innerJoinWith([ 'books', 'customer' => function ($query) { $query->where('customer.created_at > ' . (time() - 24 * 3600)); } ])->all(); // 使用left join连接 books关联,books关联再用left join 连接 author关联 $orders = Order::find()->joinWith('books.author')->all(); 在实现上,Yii 先执行满足JOIN查询条件的SQL语句,把结果填充到主模型中, 然后再为每个关联执行一条查询语句, 并填充相应的关联模型。 // Order和books关联 inner join ,但不获取books关联对应的数据 $orders = Order::find()->innerJoinWith('books', false)->all(); On条件 在定义关联的时候还可以指定on条件 class User extends ActiveRecord { public function getBooks() { return $this->hasMany(Item::className(), ['owner_id' => 'id'])->onCondition(['category_id' => 1]); } } 在 joinWith 中使用 //先查询主模型(User)的数据 SELECT user.* FROM user LEFT JOIN item ON item.owner_id=user.id AND category_id=1 // 然后再根据关联条件查询相关模型数据 SELECT * FROM item WHERE owner_id IN (...) AND category_id=1 // 这两个在查询的过程中都使用了 on条件。 $users = User::find()->joinWith('books')->all(); 如果没有使用join操作,即使用的是with 或者 直接以属性来访问关联。这个时候on条件会作为where 条件。 SELECT * FROM user WHERE id=10 $user = User::findOne(10);
24天前 喜欢(1) 浏览(160) 评论(0)
博客
<?php /** * Created by PhpStorm. * User: 16487 * Date: 2018/3/29 * Time: 11:24 */ namespace app\models; use yii\db\ActiveRecord; /** * Class AccountAR * @package app\models * @property integer $id;//ID * @property string $account_classification;//科目类别 * @property string $account_name;//科目名称 * @property string $remarks;//科目备注 */ class AccountAR extends ActiveRecord { const TABLE_NAME = 'tb_account'; const COLUMN_NAME_id = 'id'; //id const COLUMN_NAME_account_classification = 'account_classification'; //科目编号 const COLUMN_NAME_account_name = 'account_name'; //科目名称 const COLUMN_NAME_remarks = 'remarks';//科目备注 public static function tableName() { return static::TABLE_NAME; } public function attributeLabels() { return [ 'id' => 'ID', 'account_classification' => '科目编号', 'account_name' => '科目名称', 'remarks' => '备注', ]; } //添加一些常用的查询方法 /** * @param $id integer * @return static */ public static function findOneByID($id) { return static::findOne([self::COLUMN_NAME_id => $id]); } /** * @return \yii\db\ActiveQuery */ public static function getAllActiveQuery() { return static::find(); } } 代码这样写有很多好处,便于阅读以及良好的代码提示等,比如在PHPstorm下通能很友好的智能提示AccountAR::findOneByID(1)->account_name, 以及 $accountAR=new AccountAR(); accountAR->account_name='**'; 也是能友好的输入->对属性进行了提示其中@property 注释标记了该class有哪些对象和变量,以及这个变量的类型,方法中 @param 表示参数类型,这样做对调用者也是一个良好的提示,@return是返回值的类型,调用者也能通过IDE立马知道返回了什么对象。而const COLUMN_NAME_*之类的变量的作用是对应了数据表中的列名,同时也为find数据时提供了友好的便利。比如$sccountARs=AccountAR::find()->where([AccountAR::COLUMN_NAME_account_name=>$text])->all();,这样及避免了频繁的输入字符导致的错误问题也能良好的提示使用者你的字符表中有哪些字段
28天前 喜欢(1) 浏览(150) 评论(0)
博客
教程有两种,一种是别人家的教程和撩妹的教程。撩妹家的教程只是看看就好了。至于运用得看你们项目经理脾气好不好。 做了一个很是杀马特的网站 http://foxhome.top/ , SiteController actionCategory($video_type = VideoType::All) 按照美化教程生成了以下http://foxhome.top/site/sategory.html?video_type=999 对比别人家的视频网站发现一般都是 /category/999.html ,/category/122.html,/category/122.html 之类的。瞬间感觉拉低了一个档次。善于搬砖的撩妹最后折腾成了 http://foxhome.top/category/1000.html http://foxhome.top/category/1001.html http://foxhome.top/category/1002.html 详情页页弄成了 http://foxhome.top/videoinfo/3.html 。 下面是重点 对config下的 web.php进行以下修改 列如'category/<video_type:\d+>.html' => 'site/category' 中 'category/<video_type:\d+>.html' 指的是我们在浏览器中的请求<video_type:\d+>表示参数video_type,并且是数字。 当我们请求/category/1000.html 相当于将video_type转由 site/category?video_type=1000 路由去处理而category/<video_type:\d+>.html中 category和.html你想怎么写就怎么写主要是把参数video_type传递给后面的控制器即可。 如果只是想稍微修饰以下url 也可以这样子操作'/signup'=>'/user/signup' 即请求/signup相当于就是在请求/user/signup,/signup可以瞎写,但是后面的一定需要是【/控制器名称/动作】
29天前 喜欢(0) 浏览(121) 评论(0)
博客
YII2为我们提供了很多widget用来快速构建前端HTML代码,但是很多属性我们并不知道。经过一阵子瞎摸索找到了以下技巧。 YII2大部分widget都是构建了Bootstrap类标签,所以widget分为普通widget和Bootstrap widget,Bootstrap widget位于yii\bootstrap下,而一般常用的widgets位于yii\widgets或者通过yii\helpers\Html构建; 我们很多时候需要定义一个HTML元素的属性,style,id,或者class。例如 yii\helpers\Html::submitButton($content = 'Submit', $options = []); 作者为方便开发者如果需要改变widget的属性时定义options参数即可,比如我需要定义一个html标签的class则只需要定义 Html::submitButton('登录', ['class' => 'btn btn-primary', 'name' => 'login-button']),['属性'=>'属性值']; 很惊奇的发现一个widget只要有options参数则都是一样,列如定义 class ['class' => 'btn btn-primary',...], 定义html的ID ['id' => 'mView',...] 如果需要强大而优美的Bootstrap widget则只需要研究以下Bootstrap V3下的样式即可参考文档 https://v3.bootcss.com/ 以上代码生成了三个Bootstrap风格的按钮,以为YII2集成了Bootstrap css所以只要按钮中有Bootstrap的类样式就会被应用上去,至于需要哪些样式则只需要研究 https://v3.bootcss.com/css/ 可以了。 另外需要强调的是YII中有很多组合控件比如ActiveForm中的$form->field,这类控件会有一个div,再套一个input或者别的元素form->field 中的options和别处不同form->field中的options参数并不是直接定义div中的属性form->field对应的是 ActiveForm::begin中的fieldConfig变量,不同的是ActiveForm::begin是定义全部的子条目,而form->field是定义当前的一个条目。所以form->field定义外套的div的各种属性是 [...,'options'=>['class' => 'input-group'],..]因为form->field不仅能定义div元素的各种标签属性options还能定义template等。我们很多时候的需求是既要定义组合表达的div还要定义子HTML元素的各种属性 passwordInput和普通的Input区别就是多了一个type="password"属性,我们通过了上面的代码给这个input添加了一个style属性并且属性值是width: 25px; 至于options能定义什么得HTML支持什么属性,就算不支持也能加上去,至于效果嘛得看浏览器得眼色了。不管什么view一定得先找到它的options能在哪里传
29天前 喜欢(0) 浏览(123) 评论(0)
博客
有以下需求输入页数按回车跳转至某一页,效果如下 思路如下,获取到当前的url,并且获取到总页数 View: <?php $view = ''; $pageCount=$dataProvider->pagination->pageCount; if ( $pageCount> 1) { $url = $dataProvider->pagination->createUrl(0); $view = '<input id="input-gopage" style="display: inline;float: left;margin-top: 5px;width: 100px" type="number" class="btn btn-default" placeholder="共'.$pageCount.'页" data-url="' . $url . '">'; } ?> <?= GridView::widget([ 'dataProvider' => $dataProvider, 'id' => 'stock-control', 'columns' => $columns, 'formatter' => ['class' => 'yii\i18n\Formatter', 'nullDisplay' => ''], 'emptyText' => '当前没有找到库存记录', 'emptyTextOptions' => ['style' => 'min-height: 360px;color:red;font-weight:bold'], 'layout' => "<div style=\"margin:auto 2px;\" class=\"row\" id=\"stock-control-grid-view\">{items}</div>\n<div>{pager}" . $view . "</div>{summary}", 'showOnEmpty' => true, 'options' => ['class' => 'storehouse-stock-grid-view '], 'rowOptions' => ['class' => 'item-focus-base tr-item'] ]) ?> Controller: $dataProvider = new ActiveDataProvider([ 'query' => $model->getQuery()->orderBy([ 'id' => SORT_ASC,]), 'pagination' => [ 'pageSize' => 20, 'totalCount'=>$model->getQuery()->count(), ], 'key' => StockControlBean::COLUMN_NAME_sku, 'sort' => ['defaultOrder' => [StorehouseStockAR::COLUMN_NAME_id => SORT_DESC]], ]); JS: $('input#input-gopage').keyup(function (event) { if(event.keyCode==13){ var url=$(this).attr('data-url'); var url2=url.replace('page=1','page='+$(this).val()); window.location.replace(url2); } }); Controller 对 ActiveDataProvider 设置 totalCount 总数目属性,View 中 $pageCount=$dataProvider->pagination->pageCount;获取到总页数, $url = $dataProvider->pagination->createUrl(0);,构建page1 的url并且携带在input的data-url中,部分则需要监测输入回车,并且获取到url将page=1属性替换成'page='+输入的值即可, window.location.replace(url2) 实现页面跳转
29天前 喜欢(1) 浏览(152) 评论(0)
社区公告
[公告] Yii中文网为优化用户体验进行大版本升级,老版网站会维持一段时间,可以点击顶部"旧版"链接访问旧版网站。
沟通交流

:492175201(技术1群)

:183620600(技术2群)

:291010569(技术3群)