博客
通过一个例子简单说一下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) 浏览(20) 评论(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; } 这样就可以了
3天前 喜欢(0) 浏览(24) 评论(0)
博客
有些旧版的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)
博客
如果做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)
Wiki
如何在yii2表单中添加验证码?非常简单,只需要在yii2项目中做几个简单的配置就可以完成验证码的应用,yii2默认封装了许多扩展,验证码就是其中之一,那么下面就来为大家演示一下如何配置yii2的验证码 先看下验证码效果: 配置 首先,在要实用验证码的控制器(Controller)中添加下面的代码 public function actions() { return [ 'captcha' => [ 'class' => 'yii\captcha\CaptchaAction', 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, ], ]; } 然后在对应的form中添加rules规则,如下图
4天前 喜欢(0) 浏览(55) 评论(2)
博客
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就是一个快速开发的框架。
8天前 喜欢(0) 浏览(57) 评论(0)
置顶 博客
概念 把一个大型的单个应用程序和服务拆分为数个甚至数十个的支持微服务,它可扩展单个组件而不是整个的应用程序堆栈,从而满足服务等级协议。 传统的开发模式就是把所有功能都放在一个包里,基本不存在依赖,这样的优势在于开发简单,集中式管理,功能都在本地,不存在分布式的管理和调度消耗。但缺点也很明显:效率低,开发都在同一个项目改代码,相互等待,冲突不断。稳定性差,一个微小的问题,都可能导致整个应用挂掉。另外在资源利用上表现出明显的劣势,比如电商双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) 浏览(201) 评论(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) 浏览(94) 评论(0)
Wiki
1.视图页面直接加载 $css = <<<CSS p {text-indent:5em;} div{width: 500px;} CSS; $this->registerCss($css,['type'=>'text/css'],'test'); $js = <<<JS // console.log('sss'); JS; $this->registerJs($js); 视图页面直接加载css,js文件 $this->registerJsFile('@web/assets/js/site.js'); $this->registerCssFile('@web/assets/css/site.css');
12天前 喜欢(0) 浏览(92) 评论(0)
置顶 讨论
说明 课程简介 素材及源码 纠错 课程简介 本课程主要内容是如何通过使用 yii2 框架,快速打造个人博客系统。 博客系统主要分两个部分: 后台管理系统(backend) 相关内容:yii2-admin扩展, RBAC基于角色的权限管理,基于Markdown的富文本编辑器,上传图片到七牛,Gii生成代码,自定义Gii模版 前台内容展示(frontend) 相关内容:挂件的应用(widget),博客首页界面排版,文章内容呈现,博客时间史 素材及源码 为了方便大家学习 yii2 相关的知识并应用于实践中,本次教程会提供学习素材。什么是素材呢?就是基于 hyii2 后台管理系统添加了学习教程时所需要的样式文件,js文件,界面排版等。让大家免去在学习后端框架的过程中,去写前端的页面和js。更加精确的掌握 yii2 框架的实战应用。通过教程的学习可以开发出相应的一套完整的博客系统。详情查看 应用搭建 一章 为了保证课程的高品质,我们需要对素材进行收费,购买链接。购买后请联系 391430388 获取素材和源码 前台界面预览:http://hyblog.itdocs.org 管理后台地址:http://hyblog-admin.itdocs.org 账户:test 密码:123456 作者会提供完整的开发素材和成品源码,希望不要借此传播,尊重原创,万分感谢。 纠错 教程中遇到的问题,可以先自己尝试解决,培养自身的问题排查能力是一个程序员基本的要求。 本地环境问题(本站统一环境教程:PHP开发环境部署)。 代码书写错误,引起的报错。 搜索本教程错误集锦(开发中) 若仍不能解决,请联系 QQ:391430388 当然人无完人,也许教程中也存在一些不可避免的疏漏,如果发现可以联系作者,先行致谢!
精华贴
12天前 喜欢(1) 浏览(296) 评论(1)
Wiki
yii2 Carousel是一个基于JavaScript的图片轮播组件,使用Carousel可以快速的在网站任意位置放置一个图片轮播的效果 先看下效果: 代码如下: <?php echo Carousel::widget([ 'items' => [ // 只有图片的格式 '<img src="http://www.yii-china.com/statics/images/b_1.jpg"/>', // 与上面的效果一致 ['content' => '<img src="http://www.yii-china.com/statics/images/b_1.jpg"/>'], // 包含图片和字幕的格式 [ 'content' => '<img src="http://www.yii-china.com/statics/images/b_1.jpg"/>', 'caption' => '<h4>This is title</h4><p>This is the caption text</p>', //'options' => [...], //配置对应的样式 ], ] ]); ?> 在轮播幻灯片列表。每个数组元素的可配属性 [ // 必要的,轮播的内容(HTML),比如一个图像标签 'content' => '<img src="http://www.yii-china.com/statics/images/b_1.jpg'/> // 可选的,该轮播标题(HTML) 'caption' => '<h4>This is title</h4><p>This is the caption text</p>', // 可选的,轮播样式 'options' => [], ]
15天前 喜欢(0) 浏览(122) 评论(0)
Wiki
以高级版为例:打开配置文件 /frontend/config/main.php 添加以下配置: return [ ··· 'defaultRoute' => 'topic/index', ··· ]
17天前 喜欢(0) 浏览(112) 评论(0)
问题
如图: 1. 先分类,按模块分类 2. 将文档放入属于得分类或子分类,文档有目录和章节 3. 文章和章节分成两个表,章节关联目录,文章关联分类 - 这样得想法如何呢?大神们是怎样实现得呢?求解!! -- 分类表 DROP TABLE IF EXISTS `jm_category`; CREATE TABLE IF NOT EXISTS `jm_category` ( `cateid` int unsigned not null AUTO_INCREMENT PRIMARY KEY COMMENT '分类id', `title` varchar(20) not null DEFAULT '' COMMENT '标题', `parentid` int unsigned not null DEFAULT '0' COMMENT '父级分类id', `createtime` int unsigned not null DEFAULT '0' COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '分类表'; -- 创建文档 DROP TABLE IF EXISTS `jm_doc`; CREATE TABLE IF NOT EXISTS `jm_doc` ( `docid` int unsigned not null AUTO_INCREMENT PRIMARY KEY COMMENT '文档id', `docname` varchar(20) not null DEFAULT '' COMMENT '文档名', `author` varchar(20) not null DEFAULT '' COMMENT '作者', `labelimg` varchar(200) not null DEFAULT '' COMMENT '文档标签图', `description` varchar(200) not null DEFAULT '' COMMENT '描述', `version` varchar(6) not null DEFAULT '' COMMENT '文档版本', `num` int unsigned not null DEFAULT '0' COMMENT '文档阅读量', `cateid` int unsigned not null DEFAULT '0' COMMENT '文档分类id', `createtime` int unsigned not null DEFAULT '0' COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '文档表'; -- 创建目录 DROP TABLE IF EXISTS `jm_catalog`; CREATE TABLE IF NOT EXISTS `jm_catalog` ( `catalogid` int unsigned not null AUTO_INCREMENT PRIMARY KEY COMMENT '目录id', `title` varchar(50) not null DEFAULT '' COMMENT '标题', `parentid` int unsigned not null DEFAULT '0' COMMENT '父级目录', `docid` int unsigned not null DEFAULT '0' COMMENT '文档id', `createtime` int unsigned not null DEFAULT '0' COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '文档目录表'; -- 章节表 DROP TABLE IF EXISTS `jm_chapter`; CREATE TABLE IF NOT EXISTS `jm_chapter` ( `chapterid` int unsigned not null AUTO_INCREMENT PRIMARY KEY COMMENT '章节id', `title` varchar(50) not null DEFAULT '' COMMENT '标题', `content` text COMMENT '内容', `order` int not null DEFAULT '0' COMMENT '排序', `status` enum('0', '1') DEFAULT '0' COMMENT '0-整理中,1-已完成', `num` int unsigned not null DEFAULT '0' COMMENT '阅读量', `catalogid` int unsigned not null DEFAULT '0' COMMENT '目录id', `createtime` int unsigned not null DEFAULT '0' COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '章节表'; -- 文章表 DROP TABLE IF EXISTS `jm_article`; CREATE TABLE IF NOT EXISTS `jm_article` ( `articleid` int unsigned not null AUTO_INCREMENT PRIMARY KEY COMMENT '文章id', `title` varchar(50) not null DEFAULT '' COMMENT '文章标题', `author` varchar(20) not null DEFAULT '' COMMENT '作者', `summary` varchar(250) not null DEFAULT '' COMMENT '文章摘要', `content` text COMMENT '内容', `labelimg` varchar(200) not null DEFAULT '' COMMENT '文章标签图', `status` enum('0', '1') not null DEFAULT '0' COMMENT '0-未发布,1-发布', `cateid` int unsigned not null DEFAULT '0' COMMENT '分类id', `createtime` int unsigned not null DEFAULT '0' COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '文章表'; 联系方式:957770846@qq.com
21天前 喜欢(0) 浏览(195) 评论(2)
Wiki
事务在程序应用中使用非常广泛,现在提供一个 Yii2 数据库事务使用的简单示例,让大家了解在 Yii2 中如何使用数据库事务 $transaction = Yii::$app->db->beginTransaction(); try { $connection->createCommand($sql1)->execute(); //如果执行失败则抛出错误 if(status == false) throw new \Exception('这里是错误原因'); $connection->createCommand($sql2)->execute(); //只有执行了commit(),对于上面数据库的操作才会真正执行 $transaction->commit(); }catch (Exception $e) { //获取抛出的错误 $error = $e->getMessage(); //操作回滚 $transaction->rollBack(); }
22天前 喜欢(0) 浏览(142) 评论(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) 浏览(134) 评论(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) 浏览(131) 评论(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) 浏览(161) 评论(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) 浏览(196) 评论(3)
Wiki
查询一个作者(Author)写的所有书籍(Book),其中Author 和 Book 是对应的 Model 作者模型:Author Class Author extend \yii\db\ActiveRecord { ··· public function getBook() { return $this->hasMany(Book::className(), ['author_id'=>'id']); } ··· } 书籍模型:Book Class Author extend \yii\db\ActiveRecord { ··· } 查询一个id=1的作者的信息及其所写的所有书籍: $authorInfo = Author::find()->where(['id'=>1])->with('book')->one(); hasOne 是1对1,hasMany 是 1对多,写法基本一致;
24天前 喜欢(2) 浏览(144) 评论(0)
社区公告
[公告] Yii中文网为优化用户体验进行大版本升级,老版网站会维持一段时间,可以点击顶部"旧版"链接访问旧版网站。
沟通交流

:492175201(技术1群)

:183620600(技术2群)

:291010569(技术3群)