一个通用的二维码生成库,支持 HTML、PNG 和 SVG 输出格式。
Duna 二维码库是一个二维码生成工具,最初基于 Duna v8.0 之前的捆绑的 QrCode 库,由 Laurent Minguet 开发。它在 LGPL 许可下发布,为二维码生成提供灵活且开源的解决方案。
要安装该库,请使用 Composer
$ composer require duna/qrcode
以下是使用 Duna 二维码库的快速指南
首先,包含必要的类并创建一个二维码实例
<?php
use Duna\Helpers\QrCode\QrCode;
use Duna\Helpers\QrCode\Output;
$qrCode = new QrCode('Lorem ipsum dolor sit amet');
要生成二维码的 PNG 图片,指定尺寸和颜色,请使用
// Create PNG output
$output = new Output\Png();
// Generate PNG data with a specified width, background color (white), and foreground color (black)
$data = $output->output($qrCode, 100, [255, 255, 255], [0, 0, 0]);
// Save the PNG data to a file
file_put_contents('file.png', $data);
对于 SVG 输出,它对于可缩放矢量图形很有用
// Create SVG output
$output = new Output\Svg();
// Generate SVG data with a specified width, background color (white), and foreground color (black)
echo $output->output($qrCode, 100, 'white', 'black');
要将二维码显示为 HTML 表格
// Create HTML output
$output = new Output\Html();
// Generate HTML table representation of the QR code
echo $output->output($qrCode);
该库在 GNU Lesser General Public License (LGPL) v3.0 下提供。有关详细信息,请参阅 LICENSE 文件。
欢迎贡献!有关更多信息,请参阅我们的 CONTRIBUTING 指南。
对于问题和支持,请参阅我们的 问题跟踪器 或联系社区。
]]>Yii HTML 包的 3.7 版已发布。有一些改进
Script::nonce() 和 Script::getNonce() 方法用于 CSP;Select 标签。Yii Hydrator 包的 1.5 版已发布。以下是新版本中包含的改进列表
EnumTypeCaster,它将值转换为枚举;Yii Validator 包的 2.1 版已发布。以下是新版本中包含的更改列表
getRules() 方法提供的规则合并;Ip 规则中使用 Yiisoft\NetworkUtilities\IpRanges:添加 getIpRanges() 方法并弃用 getRanges()、getNetworks()、isAllowed() 方法;IpHandler 中使用 NEGATION_CHARACTER 常量,而不是声明自己的常量。有许多博客展示了如何为 Yii2 应用程序使用单独的登录,但在本文中,我将向您展示如何为所有 Yii2 高级版、Yii2 基础版、应用程序使用单个登录屏幕。它还将在您的域位于不同的服务器或同一服务器时起作用。
以下是一些实现此目的需要遵循的步骤。
1. 对于高级模板
步骤 1:将其添加到您在
/path/common/config/main.php 中的组件中
'components' => [
'user' => [
'identityClass' => 'common\models\User',
'enableAutoLogin' => true,
'identityCookie' => ['name' => '_identity', 'httpOnly' => true],
],
'request' => [
'csrfParam' => '_csrf',
],
],
步骤 2:将 Session 和 Request 添加到 main-local.php
/path/common/config/main-local.php
'components' => [
'session' => [
'cookieParams' => [
'path' => '/',
'domain' => ".example.com",
],
],
'user' => [
'identityCookie' => [
'name' => '_identity',
'path' => '/',
'domain' => ".example.com",
],
],
'request' => [
'csrfCookie' => [
'name' => '_csrf',
'path' => '/',
'domain' => ".example.com",
],
],
],
注意:example.com 是主域。所有其他域都应为此域的子域。
步骤 3:现在为所有应用程序更新相同的验证密钥
/path/frontend/config/main-local.php
/path/backend/config/main-local.php
'components' => [
'request' => [
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
'cookieValidationKey' => 'fFUeb5HDj2P-1a1FTIqya8qOE',
],
],
注意:从您前端和后端应用程序的 main.php 中删除 Session 和 request 密钥。
步骤 4:请注意,您也有一个控制台应用程序,因此在您的控制台应用程序的 main-local.php 中更新 session、user 和 request
/path/console/config/main-local.php
'components' => [
'session' => null,
'user' => null,
'request' => null,
]
2. 对于基础模板
此外,如果您为另一个项目安装了基础模板,并且您希望为此模板使用相同的登录。要实现此目的,请遵循以下步骤
步骤 1:更新您基础模板的 main-local.php
/path/basic-app/config/main-local.php
'components' => [
'session' => [
'cookieParams' => [
'path' => '/',
'domain' => ".example.com",
],
],
'user' => [
'identityCookie' => [
'name' => '_identity',
'path' => '/',
'domain' => ".example.com",
],
],
'request' => [
'csrfCookie' => [
'name' => '_csrf',
'path' => '/',
'domain' => ".example.com",
],
],
],
我希望您了解如何为所有域和子域或存储库使用单一登录。
:) 感谢阅读
]]>Yii Swagger 包的 2.1 版已发布。以下是新版本中包含的更改列表
psr/http-message 版本 ^2.0 的支持。yiisoft/yii-view 版本的最低要求提升到 ^7.1。\Yiisoft\Swagger\Action\SwaggerJson 和 \Yiisoft\Swagger\Action\SwaggerUi 操作,将 \Yiisoft\Swagger\Middleware\SwaggerJson 和 \Yiisoft\Swagger\Middleware\SwaggerUi 类标记为已弃用(它们将在下一个主要版本中删除)。swagger-api/swagger-ui 的支持。Yii 网络工具 包的 1.2 版已发布。以下是新版本中包含的更改列表
IP_PATTERN 和 IP_REGEXP 常量到 IpHelper 以检查 IPv4 和 IPv6 版本的 IP。NEGATION_CHARACTER 常量到用于否定范围的 IpRanges。isIpv4()、isIpv6()、isIp() 方法到 IpHelper。Yii 表单模型 包的第一个版本已经发布。它为表单模型提供基础,并有助于填写、验证和显示它们。
要使用,请定义一个表单模型
use Yiisoft\FormModel\Attribute\Safe;
use Yiisoft\FormModel\FormModel;
use Yiisoft\Validator\Rule\Email;
use Yiisoft\Validator\Rule\Length;
use Yiisoft\Validator\Rule\Required;
final class LoginForm extends FormModel
{
#[Label('Your login')]
#[Required]
#[Length(min: 4, max: 40, skipOnEmpty: true)]
#[Email(skipOnEmpty: true)]
private ?string $login = null;
#[Label('Your password')]
#[Required]
#[Length(min: 8, skipOnEmpty: true)]
private ?string $password = null;
#[Label('Remember me for 1 week')]
#[Safe]
private bool $rememberMe = false;
}
用数据填充它并使用表单提取器进行验证
use Psr\Http\Message\RequestInterface;
use Yiisoft\FormModel\FormHydrator;
use Yiisoft\FormModel\FormModel;
final class AuthController
{
public function login(RequestInterface $request, FormHydrator $formHydrator): ResponseInterface
{
$formModel = new LoginForm();
$errors = [];
if ($formHydrator->populateFromPostAndValidate($formModel, $request)) {
$errors = $formModel->getValidationResult()->getErrorMessagesIndexedByProperty();
}
// You can pass $formModel and $errors to the view now.
}
}
使用视图中的字段显示它
use Yiisoft\FormModel\Field;
use Yiisoft\FormModel\FormModel;
echo Field::text($formModel, 'login');
echo Field::password($formModel, 'password');
echo Field::checkbox($formModel, 'rememberMe');
// ...
]]>Yii 表单 包的第一个版本已经发布。它提供了一组小部件,有助于动态生成 HTML 表单的服务器端。以下小部件开箱即用
Checkbox、CheckboxList、Date、DateTimeLocal、Email、File、Hidden、Image、Number、Password、RadioList、Range、Select、Telephone、Text、Textarea、Time、Url;Button、ResetButton、SubmitButton;ButtonGroup、Fieldset。ErrorSummary。一般用法
use Yiisoft\Form\PureField\Field;
echo Field::text('firstName', theme: 'horizontal')
->label('First Name')
->autofocus();
echo Field::text('lastName', theme: 'horizontal')
->label('Last Name');
echo Field::select('sex')
->label('Sex')
->optionsData(['m' => 'Male', 'f' => 'Female'])
->prompt('—');
echo Field::number('age')
->label('Age')
->hint('Please enter your age.');
echo Field::submitButton('Submit')
->buttonClass('primary');
]]>Yii Hydrator 包的 1.4 版已发布。以下是新版本中包含的改进列表
ToArrayOfStrings 参数属性;Collection。Yii HTML 包的 3.6 版已发布。有一些改进和修复
Html::renderAttribute() 中抛出 InvalidArgumentException。Stringable 和数组值支持添加到文本区域标签。CheckboxList 和 RadioList 小部件。Html::renderTagAttributes() 中 null 值属性的输出。Yii Auth JWT 包的 2.1 版已发布。以下是新版本中包含的改进列表
web-token/* 包替换为一个 web-token/jwt-library,将 PHP 的最低版本更新到 8.1。^2.0 的 psr/http-message 的支持。Yii Hydrator 包的 1.3 版已发布。以下是新版本中包含的改进列表
Collection PHP 属性添加了对集合的支持;withHydrator() 方法到 ParameterAttributesHandler。getHydrator() 方法到 ParameterAttributeResolveContext。readonly 属性。Yii 网络工具 包的 1.1 版已发布。有一些改进
IpRanges,它表示一组允许或禁止的 IP 范围;Yii Validator 的主要版本已标记。
Each::PARAMETER_EACH_KEY 验证上下文参数,它在 Each 规则处理期间可用,并包含当前密钥InEnum 规则Error::getValuePath() 中 $escape 参数的类型从 bool|string|null 更改为 string|nullOneOf 和 AtLeast 规则的错误消息中列出翻译后的属性OneOf 规则中错误消息的含义OneOf 和 AtLeast 规则中错误消息的含义,并使用复数形式$min 大于 AtLeast 配置中 $attributes 的数量getName() 方法从 RuleInterface 移动到 RuleWithOptionsInterfaceRuleWithOptionsInterface 重命名为 DumpedRuleInterfaceRulesDumper 导出时,使用 FQCN 作为内置规则的名称RulesDumper 导出时,使用 FQCN 作为未实现 DumpedRuleInterface 的规则的名称$skipOnEmpty 参数的类型从 mixed 更改为 bool|callable|nullRuleHandlerInterface::validate() 中 $rule 参数的类型从 object 更改为 RuleInterfaceAtLeast 规则重命名为 FilledAtLeast,将 OneOf 规则重命名为 FilledOnlyOneOfJsonHandler 中使用内置的 PHP 函数 json_validate()Result 类中的 psalm 注释JsonHandler 中 JSON 的验证Result::add():将 array_merge() 从 foreach 中移除RulesNormalizer::normalize() 中的参数 $rules 可选Json::$message 更清晰Nested 规则中错误消息中属性名称的使用RulesNormalizer::normalize() 中使用Each::$incorrectInputKeyMessage 的type 参数的错误值查看 升级说明,其中包含有关将应用程序中的软件包升级到此主要版本的说明。
]]>我们很高兴宣布 Yii 框架版本 2.0.51 发布。
请参考 https://yiiframework.cn/download/ 中的说明安装或升级到此版本。
此版本修复了 2.0.50 中的回归,错误处理程序与 PHP 8.3 的兼容性以及一些错误。
感谢所有为框架做出贡献的 Yii 社区成员、保持文档翻译最新的翻译者以及在论坛上回答问题的社区成员。
有很多活跃的 Yii 社区,因此如果您需要帮助或想分享您的经验,请随时加入它们。
可以在 CHANGELOG 中找到完整的更改列表。
]]>一个 Yii 2 演示应用程序,用于说明 Inertia.js 的工作原理。
使用 Inertia,您可以使用经典的服务器端路由和控制器构建单页应用程序,而无需构建 API。
此应用程序是原始 用 Laravel 编写的 Ping CRM 的移植版本,并基于 Yii 2 基本项目模板。

基于 Yii 2 上的 Ping CRM 应用程序 github 和 yii 扩展。
更改:将 Vue 更新到版本 3,更新了 npm 包和 composer。将 Vue 文件转换为 Composition API(脚本设置)。
在本地克隆仓库
git clone https://github.com/toatall/pingcrm-yii2-vue3 pingcrm-yii2-vue3
cd pingcrm-yii2-vue3
安装 PHP 依赖项
composer install
安装 NPM 依赖项
npm ci
构建资产
npm run css-dev
npm run dev
创建 SQLite 数据库。您也可以使用其他数据库(MySQL、Postgres),只需相应地更新您的配置。
touch database/database.sqlite
运行数据库迁移
php yii migrate
运行数据库播种器
php yii db/seed
运行开发服务器(输出将给出地址)
php yii serve
您已准备好!在浏览器中访问 Ping CRM,并使用以下用户名和密码登录
要运行 Ping CRM 测试,请运行
(to be done)
在使用新功能扩展此项目时,需要执行以下步骤。
<?php
namespace app\controllers;
use tebe\inertia\web\Controller;
class CustomController extends Controller
{
public function actionIndex()
{
$params = [
'data' => [],
];
return $this->inertia('demo/index', $params);
}
}
您可以在 https://github.com/tbreuss/yii2-inertia 上找到更多信息。
resources/js/Pages 下为在后端添加的每个控制器操作添加一个新页面您可以在 https://inertia.laravel.net.cn 上找到更多信息。
Yii HTTP Runner 的主要版本已标记。在此版本中进行了多项更改。
NullLogger。ServerRequestFactory。SapiEmitter 标记为内部。Yii 错误处理程序 包已更新,并包含以下增强功能
@anonymous 后缀;exit(1)。Yii HTML 包的 3.5 版本发布。有一些改进
hr 标签添加了类和Html::hr() 方法;aria-describedby 属性中多个元素的支持。Yii 日志记录库 包已更新,并包含以下增强功能和新功能
Logger::assertLevelIsValid()、Logger::assertLevelIsString() 和Logger::assertLevelIsSupported();{foo.bar};DateTime 和DateTimeImmutable 支持作为日志上下文中的时间;Message::category() 方法和Message::DEFAULT_CATEGORY 常量,弃用CategoryFilter::DEFAULT 以支持它;Message::trace() 方法;Message::time() 方法;Logger::validateLevel() 方法;Logger 方法setTraceLevel() 和setExcludedTracePaths(),以支持上下文提供者的使用;Target 类中的setCommonContext() 和getCommonContext() 方法;gettype() 替换为get_debug_type() 以生成异常消息;Message 构造函数中$level 参数的类型更改为string;Yii 邮件发送器库 的次要版本已标记。有一些改进和修复
MessageFactory 中设置默认“from”值;^8.1;yiisoft/view 版本提升到^10.0。Yii 视图渲染器 的次要版本已标记。
yiisoft/view 版本提升到^10.0。RandomProvider 继承自 ActiveDataProvider,它是 Yii 2.0 PHP 框架的一部分。它以随机方式选择记录,在某些情况下,这可能比常规ActiveDataProvider(通常)的有序方式更具吸引力。RandomProvider 旨在与我的 LoadMorePager 协同工作,但它也可以与 LinkPager 或其他分页器一起使用。
请注意,RandomProvider 不支持CUBRID 或dblib 数据库驱动程序。此外,我只在mysql 中对其进行了测试。我相信它可以与其他驱动程序一起使用。如果您有任何经验可以分享,我将不胜感激。
另外请注意,RandomProvider 使用一种名为“Order By Rand()”的算法。这很慢,而且扩展性不强。因此,建议仅在数据量相对较小的数据集(少于几千条记录)中使用RandomProvider。更多信息 这里。
RandomProvider 的演示 这里。
以通常的方式使用 Composer 安装 yii2-random-provider。在您的composer.json 文件的require 部分添加以下内容
"sjaakp/yii2-random-provider": "*"
或运行
composer require sjaakp/yii2-random-provider
您可以通过 下载 ZIP 格式的源代码 手动安装 yii2-random-provider。
RandomProvider 是 Yii 的 ActiveDataProvider 的直接替换。只需像使用ActiveDataProvider 一样使用它。
]]>本项目为yii2-debug的扩展,使用MongoDB对debug数据进行存储。
src/ 代码目录
src/models/ 数据模型
src/views/ 视图文件
src/controllers/ 控制器
composer require yagas/yii2-debug4mongo
if (YII_ENV_DEV) {
$config['bootstrap'][] = 'debug';
$config['modules']['debug'] = [
'class' => 'yagas\debug\Module',
'logTarget' => [
'class' => 'yagas\debug\LogTarget',
'app_no' => 'localhost_001', // 为当前站点设定标识
],
'percent' => 10, // 百分之十的几率清除历史数据(GC)
];
}
]]>使用 PDF.js 预览 PDF 文件,用于 Yii2
Yii2 PDF.js 使用 PDF.js
演示:https://mozilla.github.io/pdf.js/web/viewer.html
包可在 Packagist 上获取,您可以使用 Composer 安装它。
composer require diecoding/yii2-pdfjs '^1.0'
或添加到您的composer.json 文件的 require 部分。
'diecoding/yii2-pdfjs': '^1.0'
...
'modules'=>[
'pdfjs' => [
'class' => \diecoding\pdfjs\Module::class,
],
],
...
echo \diecoding\pdfjs\PdfJs::widget([
'url' => '@web/uploads/dummy.pdf',
]);
echo Url::to(["/pdfjs", 'file' => Url::to('@web/uploads/dummy.pdf', true)], true);
echo \diecoding\pdfjs\PdfJs::widget([
'url' => '@web/uploads/dummy.pdf',
'options' => [
'style' => [
'width' => '100%',
'height' => '500px',
],
],
]);
echo \diecoding\pdfjs\PdfJs::widget([
'url' => '@web/uploads/dummy.pdf',
'sections' => [
'toolbarContainer' => false,
],
]);
]]>消息队列主要用于业务解耦,本项目采用rabbitmq,支持thinkPHP,laravel,webman,yii等常用框架,也可以单独使用。
composer require xiaosongshu/rabbitmq
<?php
namespace app\commands;
require_once __DIR__.'/vendor/autoload.php';
class Demo extends \Xiaosongshu\Rabbitmq\Client
{
/** 以下是rabbitmq配置 ,请填写您自己的配置 */
/** @var string $host 服务器地址 */
public static $host = "127.0.0.1";
/** @var int $port 服务器端口 */
public static $port = 5672;
/** @var string $user 服务器登陆用户 */
public static $user = "guest";
/** @var string $pass 服务器登陆密码 */
public static $pass = "guest";
/**
* 业务处理
* @param array $params
* @return int
*/
public static function handle(array $params): int
{
//TODO 这里写你的业务逻辑
// ...
var_dump($params);
return self::ACK;
//return self::NACK;
}
}
\app\commands\Demo::publish(['name'=>'tome','age'=>15]);
你可以在任何地方投递消息。
\app\commands\Demo::consume();
你可以把消费者放到command命令行里面,使用命令行执行队列消费。举个例子(这里以yii为例子,你也可以换成laravel,webman,thinkPHP等其他框架): `php <?php
namespace app\commands;
use yii\console\Controller;
/**
@note 我只是一个例子 */ class QueueController extends Controller {
/**
开启消费者命令 consume
```bash
php yii queue/index
注:如果你需要开启多个消费者,那么可以在多个窗口执行开启消费者命令即可。当然你也可以使用多进程来处理。
\app\commands\Demo::close();
队列使用过程中请使用 \RuntimeException和\Exception捕获异常
本项目根目录有一个demo.php的测试文件,可以复制到你的项目根目录,在命令行窗口直接在命令行执行以下命令即可。 `php php demo.php 测试文件代码如下:php <?php
namespace xiaosongshu\test; require_once DIR . '/vendor/autoload.php';
/**
@purpose 定义一个队列演示 */ class Demo extends \Xiaosongshu\Rabbitmq\Client {
/* 以下是rabbitmq配置 ,请填写您自己的配置 / /* @var string $host 服务器地址 / public static $host = "127.0.0.1";
/* @var int $port 服务器端口 / public static $port = 5672;
/* @var string $user 服务器登陆用户 / public static $user = "guest";
/* @var string $pass 服务器登陆密码 / public static $pass = "guest";
/**
/ 投递普通消息 */ \xiaosongshu\test\Demo::publish(['name' => 'tom']); \xiaosongshu\test\Demo::publish(['name' => 'jim']); \xiaosongshu\test\Demo::publish(['name' => 'jack']); /* 开启消费,本函数为阻塞,后面的代码不会执行 / \xiaosongshu\test\Demo::consume(); / 关闭消费者 */ \xiaosongshu\test\Demo::close(); `
composer require xiaosongshu/yii2-rabbitmq
<?php
namespace app\commands;
require_once __DIR__.'/vendor/autoload.php';
class Demo extends \Xiaosongshu\Rabbitmq\Client
{
/** 以下是rabbitmq配置 ,请填写您自己的配置 */
/** @var string $host 服务器地址 */
public static $host = "127.0.0.1";
/** @var int $port 服务器端口 */
public static $port = 5672;
/** @var string $user 服务器登陆用户 */
public static $user = "guest";
/** @var string $pass 服务器登陆密码 */
public static $pass = "guest";
/**
* 业务处理
* @param array $params
* @return int
*/
public static function handle(array $params): int
{
//TODO 这里写你的业务逻辑
// ...
var_dump($params);
return self::ACK;
//return self::NACK;
}
}
\app\commands\Demo::publish(['name'=>'tome','age'=>15]);
你可以在任何地方投递消息。
\app\commands\Demo::consume();
你可以把消费者放到command命令行里面,使用命令行执行队列消费。举个例子: `php <?php
namespace app\commands;
use yii\console\Controller;
/**
@note 我只是一个例子 */ class QueueController extends Controller {
/**
开启消费者命令 consume
```bash
php yii queue/index
队列使用过程中请使用 \RuntimeException和\Exception捕获异常
composer require xiaosongshu/yii2-elasticsearch
`php
'components' => [
'ESClient' => [
'class' => \Xiaosongshu\Elasticsearch\ESClient::class,
'node'=>['192.168.101.170:9200'],
'username' => '',
'password' => '',
],
]
`
$res = Yii::$app->ESClient->search('index','_doc','title','测试')['hits']['hits'];
创建索引:createIndex
创建表结构:createMappings
删除索引:deleteIndex
获取索引详情:getIndex
新增一行数据:create
批量写入数据:insert
根据id批量删除数据:deleteMultipleByIds
根据Id 删除一条记录:deleteById
获取表结构:getMap
根据id查询数据:find
根据某一个关键字搜索:search
使用原生方式查询es的数据:nativeQuerySearch
多个字段并列查询,多个字段同时满足需要查询的值:andSearch
or查询 多字段或者查询:orSearch
根据条件删除数据:deleteByQuery
根据权重查询:searchByRank
获取所有数据:all
添加脚本:addScript
获取脚本:getScript
使用脚本查询:searchByScript
使用脚本更新文档:updateByScript
索引是否存在:IndexExists
根据id更新数据:updateById
<?php
require_once 'vendor/autoload.php';
/** 实例化客户端 */
$client = new \Xiaosongshu\Elasticsearch\ESClient([
/** 节点列表 */
'nodes' => ['192.168.4.128:9200'],
/** 用户名 */
'username' => '',
/** 密码 */
'password' => '',
]);
/** 删除索引 */
$client->deleteIndex('index');
/** 如果不存在index索引,则创建index索引 */
if (!$client->IndexExists('index')) {
/** 创建索引 */
$client->createIndex('index', '_doc');
}
/** 创建表 */
$result = $client->createMappings('index', '_doc', [
'id' => ['type' => 'long',],
'title' => ['type' => 'text', "fielddata" => true,],
'content' => ['type' => 'text', 'fielddata' => true],
'create_time' => ['type' => 'text'],
'test_a' => ["type" => "rank_feature"],
'test_b' => ["type" => "rank_feature", "positive_score_impact" => false],
'test_c' => ["type" => "rank_feature"],
]);
/** 获取数据库所有数据 */
$result = $client->all('index','_doc',0,15);
/** 写入单条数据 */
$result = $client->create('index', '_doc', [
'id' => rand(1,99999),
'title' => '我只是一个测试呢',
'content' => '123456789',
'create_time' => date('Y-m-d H:i:s'),
'test_a' => 1,
'test_b' => 2,
'test_c' => 3,
]);
/** 批量写入数据 */
$result = $client->insert('index','_doc',[
[
'id' => rand(1,99999),
'title' => '我只是一个测试呢',
'content' => '你说什么',
'create_time' => date('Y-m-d H:i:s'),
'test_a' => rand(1,10),
'test_b' => rand(1,10),
'test_c' => rand(1,10),
],
[
'id' => rand(1,99999),
'title' => '我只是一个测试呢',
'content' => '你说什么',
'create_time' => date('Y-m-d H:i:s'),
'test_a' => rand(1,10),
'test_b' => rand(1,10),
'test_c' => rand(1,10),
],
[
'id' => rand(1,99999),
'title' => '我只是一个测试呢',
'content' => '你说什么',
'create_time' => date('Y-m-d H:i:s'),
'test_a' => rand(1,10),
'test_b' => rand(1,10),
'test_c' => rand(1,10),
],
]);
/** 使用关键字搜索 */
$result = $client->search('index','_doc','title','测试')['hits']['hits'];
/** 使用id更新数据 */
$result1 = $client->updateById('index','_doc',$result[0]['_id'],['content'=>'今天你测试了吗']);
/** 使用id 删除数据 */
$result = $client->deleteById('index','_doc',$result[0]['_id']);
/** 使用条件删除 */
$client->deleteByQuery('index','_doc','title','测试');
/** 使用关键字搜索 */
$result = $client->search('index','_doc','title','测试')['hits']['hits'];
/** 使用条件更新 */
$result = $client->updateByQuery('index','_doc','title','测试',['content'=>'哇了个哇,这么大的种子,这么大的花']);
/** 添加脚本 */
$result = $client->addScript('update_content',"doc['content'].value+'_'+'谁不说按家乡好'");
/** 添加脚本 */
$result = $client->addScript('update_content2',"(doc['content'].value)+'_'+'abcdefg'");
/** 获取脚本内容 */
$result = $client->getScript('update_content');
/** 使用脚本搜索 */
$result = $client->searchByScript('index', '_doc', 'update_content', 'title', '测试');
/** 删除脚本*/
$result = $client->deleteScript('update_content2');
/** 使用id查询 */
$result = $client->find('index','_doc','7fitkYkBktWURd5Uqckg');
/** 原生查询 */
$result = $client->nativeQuerySearch('index',[
'query'=>[
'bool'=>[
'must'=>[
[
'match_phrase'=>[
'title'=>'测试'
],
],
[
'script'=>[
'script'=>"doc['content'].value.length()>2"
]
]
]
]
]
]);
/** and并且查询 */
$result = $client->andSearch('index','_doc',['title','content'],'测试');
/** or或者查询 */
$result = $client->orSearch('index','_doc',['title','content'],'今天');
将本扩展包的phpunit.xml文件复制到项目的根目录下面然后执行下面的命令 `bash php ./vendor/bin/phpunit -c phpunit.xml `
2723659854@qq.com
]]>$config['components']['mailer'] = [
'class' => 'jatin\resend\Mailer',
'useFileTransport' => false,
'viewPath' => '@app/mail',
'transport' => [
'apiKey' => '<YOUR_API_KEY>'
],
];
]]>
这是一个针对 Yii2 框架的简单代理。此扩展为 Yii 框架 2.0 提供了 HTTP 代理操作。
有关许可证信息,请查看 LICENSE 文件。
composer require asminog/yii2-proxy
use asminog\proxy\ProxyAction;
class SiteController extends Controller
{
public function actions()
{
return [
'proxy' => [
'class' => ProxyAction::class,
// 'accessToken' => 'your-access-token', // - set access token for secure requests
// 'throw404Exception' => true, // - show 404 error if access token is not valid or request url is not valid
// 'proxyHeaders' => ['User-Agent', 'Content-Type'], // - set headers for proxy request
// 'proxyCookies' => ['cookie1', 'cookie2'], // - set cookies for proxy request
],
];
}
}
]]>一个针对 bootstrap 5 的 Yii2 表单向导小部件

安装此扩展的首选方法是通过 composer。
运行
php composer.phar require --prefer-dist sandritsch91/yii2-form-wizard
或添加
"sandritsch91/yii2-form-wizard": "*"
到您的 composer.json 文件的 require 部分。
use sandritsch91\yii2-form-wizard\FormWizard;
echo FormWizard::widget([
// required
'model' => $model, // The model to be used in the form
'tabOptions' => [ // These are the options for the Bootstrap Tab widget
'items' => [
[
'label' => 'Step 1', // The label of the tab, if omitted, a default-label will be used (Step 1, Step 2, ...)
'content' => $this->render('_step1', ['model' => $model]), // Either the content of the tab
],
[
'label' => 'Step 2',
'view' => '/test/_step2', // or a view to be rendered. $model and $form are passed to the view
'params' => ['a' => 1, 'b' => 2] // Pass additional parameters to the view
]
],
'navType' => 'nav-pills'
],
// optional
'validateSteps' => [ // Optional, pass the fields to be validated for each step.
['name', 'surname'],
[], // Leave array empty if no validation is needed
['email', 'password']
],
'options' => [], // Wizard-container html options
'formOptions' => [], // Form html options
'buttonOptions' => [ // Button html options
'previous' => [
'class' => ['btn', 'btn-secondary'],
'data' => [
'formwizard' => 'previous' // If you change this, make sure the clientOptions match
]
],
'next' => [...],
'finish' => [...]
],
'clientOptions' => [ // Client options for the form wizard, if you need to change them
// 'finishSelector' => '...',
// 'nextSelector' => '...',
// 'previousSelector' => '...',
// 'keepPosition' => true // Keep scroll position on step change.
// Set to false to disable, or pass a selector if you have a custom scroll container.
// Defaults to true.
],
'clientEvents' => [ // Client events for the form wizard
// 'onNext' => 'function () {...}',
// 'onPrevious' => 'function () {...}',
// 'onFinish' => 'function (){...}'
]
]);
欢迎贡献。
如果您有任何问题、想法、建议或错误,请打开一个问题。
此包使用 codeception 进行测试。要运行测试,请运行以下命令
#### Unit tests
run ```php.exe .\vendor\bin\codecept run Unit``` in the root directory of this repository.
#### Functional tests
run ```php.exe .\vendor\bin\codecept run Functional``` in the root directory of this repository.
#### Accpetance tests
To be able to run acceptance tests, a few requirements are needed:
For Windows:\
- install java runtime environment
- install nodejs
- install selenium-standalone: `npm install -g selenium-standalone`
- start selenium-standalone: `selenium-standalone install && selenium-standalone start`
- host a yii2 application on a server or locally via ```./yii serve```
- add this plugin as a dependency to your ```composer.json``` and update dependencies
- site must be reachable over http://formwizard.com/
- add an action ```actionTest``` to the ```SiteController```, as described below
- this action must return a view file, as described below
- run ```php.exe .\vendor\bin\codecept run Acceptance```
For Linux:
Never did that before, but I think it is similar to the Windows setup.
The action in the SiteController:
```php
public function actionTest(): string
{
include __DIR__ . '/../vendor/sandritsch91/yii2-widget-form-wizard/tests/Support/Data/models/User.php';
$model = new User();
if (Yii::$app->request->post() && $model->load(Yii::$app->request->post()) && $model->validate()) {
return 'success';
}
return $this->render('test', [
'model' => new User()
]);
}
```
The view returned by the action:
```php
/** @var User $model */
use sandritsch91\yii2\formwizard\FormWizard;
use sandritsch91\yii2\formwizard\tests\Support\Data\models\User;
$wizard = FormWizard::widget([
'model' => $model,
'tabOptions' => [
'options' => [
'class' => 'mb-3'
],
'items' => [
[
'label' => 'Step 1',
'view' => '@app/vendor/sandritsch91/yii2-widget-form-wizard/tests/Support/Data/views/site/step1',
'linkOptions' => [
'id' => 'step1-link',,
'params' => [
'test' => 'some test variable'
]
]
],
[
'label' => 'Step 2',
'view' => '@app/vendor/sandritsch91/yii2-widget-form-wizard/tests/Support/Data/views/site/step2',
'linkOptions' => [
'id' => 'step2-link',
]
],
[
'label' => 'Step 3',
'view' => '@app/vendor/sandritsch91/yii2-widget-form-wizard/tests/Support/Data/views/site/step3',
'linkOptions' => [
'id' => 'step3-link',
]
]
],
'navType' => 'nav-pills'
],
'validateSteps' => [
['firstname', 'lastname'],
['username', 'password', 'password_validate'],
['email']
],
'clientOptions' => [
'keepPosition' => true
]
]);
echo \yii\helpers\Html::tag('div', $wizard, [
'class' => 'col-4'
]);
```
After the initial installation, you only have to start the selenium-standalone server ```selenium-standalone start```
and run the tests ```php.exe .\vendor\bin\codecept run Acceptance``` in the root directory of this repository.
If you do not want to setup an application, just run the unit and functional tests by
running ```php.exe .\vendor\bin\codecept run Unit,Functional```, I can modify and run the acceptance tests for you,
after you opened a pull request.
]]>我最近被分配的任务是将几个扩展表单集成到 WordPress 网站中。这些表单包含许多字段、复杂的验证规则、动态字段(一对多关系),甚至相互依赖关系,其中使用 PHP 继承可以减少代码重复。
在最初的探索中,很明显,在 WordPress 中处理表单的传统方法通常涉及安装插件或使用编辑器或自定义页面模板手动嵌入标记。随后,很大程度上依赖于插件的功能来管理表单提交,或者诉诸自定义编码。
鉴于我的任务的一部分包括记录数据、与 API 端点交互、发送电子邮件等等,我选择自己开发功能,而不是验证现有插件是否支持这些要求。
此外,考虑到当前情况(截至 2024 年 3 月),大多数 Yii 3 包被认为是生产就绪的,根据官方资料,并且作为 Yii 框架的长期用户,我认为这是一个探索和熟悉这些更新的绝佳时机。
您可以通过访问 Github 上的整个项目来探索并查看代码。
此外,您可以轻松地使用 Docker 部署它,只需从项目的根目录执行 docker-compose up 即可。检查 Dockerfile 以获取 WordPress 设置和内容生成,这些操作会自动完成。
我的目标是在利用 Yii3 包的 WordPress 框架中呈现和管理表单。为了演示目的,我选择实现一个基本的评分表单,其中重点仅在于验证数据,而不执行进一步的操作。
要继续,让我们从一个极简的经典主题作为示例开始。我在仪表盘中创建了一个名为“评分表单”的 WordPress 页面。然后,需要在主题的根文件夹中创建一个名为 page-the-rating-form.php 的文件来显示此特定页面。
此指定文件用作定义表单标记的蓝图。
为了利用 Yii3 的功能,我们将合并以下包
首先,让我们通过执行 composer init 在主题的根目录中初始化一个 Composer 项目。此过程将生成一个 composer.json 文件。随后,我们将继续将 Yii3 包包含到我们的项目中。
composer require yiisoft/form-model:dev-master yiisoft/validator yiisoft/form:dev-master
并指示主题加载 composer 自动加载,方法是将以下行添加到 functions.php 文件中
require __DIR__ . '/vendor/autoload.php';
执行 composer init 命令后,将在主题的根目录中创建一个 src 目录。现在我们将继续在此目录中添加我们的表单模型类。
预见项目的扩展,必须保持组织。因此,我们将创建目录 src/Forms 并将 RatingForm 类放置在其中。
<?php
namespace Glpzzz\Yii3press\Forms;
use Yiisoft\FormModel\FormModel;
class RatingForm extends FormModel
{
private ?string $name = null;
private ?string $email = null;
private ?int $rating = null;
private ?string $comment = null;
private string $action = 'the_rating_form';
public function getPropertyLabels(): array
{
return [
'name' => 'Name',
'email' => 'Email',
'rating' => 'Rating',
'comment' => 'Comment',
];
}
}
除了我们评分用例所需的必填字段之外,观察 action 类属性至关重要。此属性非常重要,因为它指示 WordPress 哪个主题钩子应该管理表单提交。对此将作进一步说明。
现在,让我们将一些验证规则合并到模型中,以确保输入完整性。最初,我们将配置类以实现 RulesProviderInterface。这使表单包能够访问这些规则并使用本机验证属性增强 HTML 标记。
class RatingForm extends FormModel implements RulesProviderInterface
现在我们需要在类上实现 getRules() 方法。
public function getRules(): iterable
{
return [
'name' => [
new Required(),
],
'email' => [
new Required(),
new Email(),
],
'rating' => [
new Required(),
new Integer(min: 0, max: 5),
],
'comment' => [
new Length(min: 100),
],
];
}
要生成表单标记,我们需要将 RatingForm 的实例传递给模板。在 WordPress 中,我采用的方法是在呈现页面之前创建一个全局变量(不可否认,这不是最优雅的解决方案)。
$hydrator = new Hydrator(
new CompositeTypeCaster(
new NullTypeCaster(emptyString: true),
new PhpNativeTypeCaster(),
new HydratorTypeCaster(),
)
);
add_filter('template_redirect', function () use ($hydrator) {
// Get the queried object
$queried_object = get_queried_object();
// Check if it's a page
if ($queried_object instanceof WP_Post && is_page()) {
if ($queried_object->post_name === 'the-rating-form') {
global $form;
if ($form === null) {
$form = $hydrator->create(RatingForm::class, []);
}
}
}
});
值得注意的是,我们已在任何特定函数之外实例化了 Hydrator 类,使我们能够将其重新用于所有必要的回调。现在 RatingForm 实例可用后,我们将继续在 page-the-rating-form.php 文件中为表单制作标记。
<?php
use Glpzzz\Yii3press\Forms\RatingForm;
use Yiisoft\FormModel\Field;
use Yiisoft\Html\Html;
/** @var RatingForm $form */
global $form;
?>
<?php get_header(); ?>
<h1><?php the_title(); ?></h1>
<?php the_content(); ?>
<?= Html::form()
->post(esc_url(admin_url('admin-post.php')))
->open()
?>
<?= Field::hidden($form, 'action')->name('action') ?>
<?= Field::text($form, 'name') ?>
<?= Field::email($form, 'email') ?>
<?= Field::range($form, 'rating') ?>
<?= Field::textarea($form, 'comment') ?>
<?= Html::submitButton('Send') ?>
<?= "</form>" ?>
<?php get_footer(); ?>
在表单的标记生成中,我们利用了 Yii3 的 Html 助手和 Field 类的组合。值得注意的要点包括
admin-post.php WordPress 端点。action 值,我们使用了一个名为 'action' 的隐藏字段。我们选择将输入重命名为 'action',因为 Field::hidden 方法以 TheFormClassName[the_field_name] 格式生成字段名,而我们需要将其简单地命名为 'action'。此调整便于挂钩到主题函数以处理表单请求,如下一节所述。
在深入研究之前,让我们利用 Yii 的功能来增强表单。虽然我们已经在模型中定义了用于验证提交后输入的验证规则,但在浏览器中验证输入也是有利的。虽然我们可以重复定义这些验证规则直接在输入元素上,但 Yii 提供了一种简化的方式。通过将以下代码片段合并到 functions.php 文件中
add_action('init', function () {
ThemeContainer::initialize([
'default' => [
'enrichFromValidationRules' => true,
]
], 'default', new ValidationRulesEnricher()
);
});
通过实现此代码片段,我们为默认的表单主题激活了 ValidationRulesEnricher。激活后,我们会注意到表单字段现在被丰富了验证规则,例如“required”、“min”和“max”,与之前在模型类中定义的验证规则一致。此功能简化了流程,节省了宝贵的时间,并最大程度地减少了手动编写代码的需求。事实上,这展示了 Yii3 提供的一些出色功能。
当表单提交时,它被定向到 admin-post.php,这是一个由 WordPress 提供的端点。但是,当处理多个表单时,区分每个表单的处理变得至关重要。这就是在 POST 请求中包含 action 值非常宝贵的地方。
请注意以下代码片段中的前两行:钩子的命名约定是 admin_post_<action_name>。因此,如果一个表单有 action = 'the-rating-form',相应的钩子名称将是 admin_post_the_rating_form。
至于同时包含 admin_post_<action_name> 和 admin_post_nopriv_<action_name>,这是因为 WordPress 允许使用不同的处理程序,具体取决于用户是否登录。在我们的场景中,我们要求相同的处理程序,无论用户的身份验证状态如何。
add_action('admin_post_the_rating_form', fn() => handleForms($hydrator));
add_action('admin_post_nopriv_the_rating_form', fn() => handleForms($hydrator));
function handleForms(Hydrator $hydrator): void
{
global $form;
$form = $hydrator->create(RatingForm::class, $_POST['RatingForm']);
$result = (new Yiisoft\Validator\Validator())->validate($form);
if ($form->isValid()) {
// handle the form
}
get_template_part('page-the-rating-form');
}
回到 Yii 方面:我们使用 hydrator 实例化并加载发布的数据到表单中。然后,我们继续验证数据。如果验证成功通过,我们可以使用经过验证的数据继续执行预期操作。但是,如果验证失败,我们将重新渲染表单,并使用提交的数据以及验证期间生成的任何错误消息填充它。
最初发布于 https://glpzzz.dev/2024/03/03/integrating-yii3-packages-into-wordpress.html
]]>记录 Yii2 的 ActiveRecord 模型的更改。
此包允许维护模型更改的历史记录,提供有关可能差异或异常的信息,这些信息可能表明可疑活动。接收和存储的信息可以随后以多种方式部署。
安装此扩展的最佳方式是通过 composer。
然后执行
php composer.phar require --prefer-dist neoacevedo/yii2-auditing "*"
或添加
"neoacevedo/yii2-auditing": "*"
到您的 composer.json 文件的 require 部分。
安装扩展后,在应用程序的控制台配置文件中,在 migrationPath 区域添加
...
'@vendor/neoacevedo/yii2-auditing/neoacevedo/auditing/migrations',
...
然后,在模型代码中,在 behaviors 方法内添加
public function behaviors()
{
return [
[
'class' => \neoacevedo\auditing\behaviors\AuditBehavior::class,
'deleteOldData' => true, // Para borrar datos antiguos del registro de eventos
'deleteNumRows' => 20, // Borra esta cantidad de registros
],
...
];
}
您可以像在 Web 应用程序中实现的任何模型一样部署信息。
您可以使用一个控制器和一个使用 GridView 的视图来列出历史记录。例如,您可以创建一个名为 AuditingController 的控制器,并创建如下的 actionIndex 方法
/**
* Lists all Auditing models.
*
* @return string
*/
public function actionIndex()
{
$searchModel = new AuditingSearch();
$dataProvider = $searchModel->search($this->request->queryParams);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
要查看数据,请创建 actionView 方法
/**
* Displays a single Auditing model.
* @param int $id ID
* @return string
* @throws NotFoundHttpException if the model cannot be found
*/
public function actionView($id)
{
return $this->render('view', [
'model' => $this->findModel($id),
]);
}
在 view 视图中,您可以添加 GridView 来列出历史记录
...
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
'id',
'user_id',
'description',
'event',
'model',
'attribute',
'old_value',
'new_value',
'action',
'ip',
'created_at',
],
]); ?>
...
]]>用于 DM 数据库的数据库扩展
安装此扩展的首选方法是通过 composer。
运行
php composer.phar require --prefer-dist luguohuakai/yii2-dm "*"
或添加
"luguohuakai/yii2-dm": "*"
到您的 composer.json 文件的 require 部分。
安装扩展后,只需在代码中使用它
'components' => [
'db' => [
'class' => 'luguohuakai\db\dm\Connection',
'dsn' => 'dm:host=localhost:xxx;schema=xxx',
'username' => 'SYSDBA',
'password' => 'SYSDBA',
]
]
]]>DataTable 小部件用于创建交互式和动态数据表。提供的 JavaScript 代码演示了如何使用服务器端处理、自定义数据处理以及列渲染和完整服务器端导出来初始化 DataTable。
`composer require rashedalkhatib/yii2-datatables:1.0.0``../frontend/assets/AppAsset.php`rashedalkhatib\datatables\DataTableAsset 添加到您的 $depends 数组中 public $depends = [
'yii\web\YiiAsset',
'yii\bootstrap\BootstrapAsset',
'yii\bootstrap\BootstrapPluginAsset',
'rashedalkhatib\datatables\DataTableAsset'
];
$searchFormSelector = '#searchForm';
$ajaxUrl = Url::to(['api/yourEndPoint']); // Adjust the URL based on your routes
// Define your DataTable columns
$columns = [
[
'title' => 'ID',
'data' => 'id',
'visible' => true,
'render' => new JsExpression('function(data, type, row) {
return "demo";
}'),
],
];
// Configure other DataTable parameters
$processing = true;
$serverSide = true;
$pageLength = 10;
$dom = 'Btip';
$buttons = [
[
'extend' => 'excel',
'text' => 'Excel',
'titleAttr' => 'Excel',
'action' => new JsExpression('exportAll') // this is required
],
];
// Configure Ajax settings
$ajaxConfig = [
'url' => $ajaxUrl,
'bdestroy' => true,
'type' => 'POST',
'data' => new JsExpression('function(d) {
var searchForm = $('body').find('#searchForm').serializeArray();
searchForm[searchForm.length] = { name: 'YourModel[page]', value: d.start }; // required
searchForm[searchForm.length] = { name: 'YourModel[length]', value: d.length }; // required
searchForm[searchForm.length] = { name: 'YourModel[draw]', value: d.draw }; // required
var order = {
'attribute': d.columns[d.order[0]['column']]['data'],
'dir': d.order[0]['dir']
}; // required
searchForm[searchForm.length] = { name: 'YourModel[order]', value: JSON.stringify(order) };
return searchForm;
}'),
'dataSrc' => new JsExpression('function(d) {
var searchForm = $("' . $searchFormSelector . '").serializeArray();
if (d.validation) {
searchForm.yiiActiveForm("updateMessages", d.validation, true);
return [];
}
return d.data;
}'),
];
// Use the DataTableWidget with configured parameters
DataTable::widget([
'id' => 'yourDataTable',
'ajaxConfig' => $ajaxConfig,
'columns' => $columns,
'processing' => $processing,
'serverSide' => $serverSide,
'pageLength' => $pageLength,
'dom' => $dom,
'buttons' => $buttons,
]);
// The HTML container for your DataTable
echo '<form id="searchForm">// your inputs </form>';
echo '<table id="yourDataTable" class="display"></table>';
<form id="searchForm">
// your inputs
</form>
<table id="yourDataTable" class="display" style="width:100%">
</table>
var arrayToExport = [0,1];
$('#yourDataTable').DataTable({
"ajax": {
// Server-side processing configuration
"url": "../api/yourEndPoint",
"bdestroy": true, // this allows you to re init the dataTabel and destory it
"type": "POST", // request method
"data": function (d) { // this represent the data you are sending with your ajax request
// Custom function for sending additional parameters to the server
var searchForm = $('body').find('#searchForm').serializeArray();
searchForm[searchForm.length] = { name: "YourModel[page]", value: d.start }; // required
searchForm[searchForm.length] = { name: "YourModel[length]", value: d.length }; // required
searchForm[searchForm.length] = { name: "YourModel[draw]", value: d.draw }; // required
var order = {
'attribute': d.columns[d.order[0]['column']]['data'],
'dir': d.order[0]['dir']
}; // required
searchForm[searchForm.length] = { name: "YourModel[order]", value: JSON.stringify(order) };
return searchForm;
},
dataSrc: function (d) {
// Custom function to handle the response data
// EX:
var searchForm = $('body').find('#searchForm').serializeArray();
if (d.validation) {
searchForm.yiiActiveForm('updateMessages', d.validation, true);
return [];
}
return d.data;
}
},
"columns": [{
// Column configurations
"title": "ID",
"data": "id",
"visible": true // visablity of column
},
// ... (other columns)
{
"title": "Actions",
"data": "id",
"visible": actionCol,
"render": function (data, type, row) {
// Custom rendering function for the "Actions" column
return '<a class="showSomething" data-id="' + row.id + '">View</a>';
}
}],
processing: true,
serverSide: true,
"pageLength": 10,
dom: "Btip",
"buttons": [{
// "Excel" button configuration
"extend": 'excel',
exportOptions: {
columns: arrayToExport
},
"text": ' Excel',
"titleAttr": 'Excel',
"action": exportAll // newexportaction this action is to allow you exporting with server side without rendaring data
}],
});
// in your HTTP request you want to include these params
$_postData = [
'page' => $this->page == 0 ? 0 : $this->page / $this->length, // this equation is required to handle Yii2 Data provider Logic
'limit' => $this->length,
'export' => $this->export,
'order' => $this->order,
// add your custom params .....
];
return $this->asJson(
[
'data' => $_scoreData->data,
'draw' => $_scoreSearchForm->draw,
'recordsTotal' => $_scoreData->count,
'recordsFiltered' => $_scoreData->count
]);
public function actionYourEndPoint()
{
$searchModel = new SearchModel();
$dataProvider = $searchModel->search(Yii::$app->request->get());
return $this->asJson(
array(
'data' => $dataProvider['data'],
'count' => $dataProvider['count']
)
);
}
public function search($params)
{
$this->load($params, ''); // load your values into the model
$query = Data::find(); // Data model is your link to the database
$_order = json_decode($this->order);
if ($this->export == 'true') {
$dataProvider = new ActiveDataProvider([
'query' => $query
// we removed the page and pageSize keys to allow all data to be exported
]);
} else {
$_orderType = SORT_ASC;
if ($_order->dir == 'desc')
$_orderType = SORT_DESC;
$query->orderBy([$_order->attribute => $_orderType]);
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => $this->limit,
'page' => $this->page,
],
]);
}
return array(
'data' => $dataProvider->getModels(),
'count' => $dataProvider->getTotalCount()
);
}
此行为会在验证之前自动将属性从 JSON 解码为数组,处理错误并在验证失败时重新编码。这样,就可以进一步处理“真实”json 字符串。
安装此扩展的最佳方式是通过 composer。
运行
composer require --prefer-dist eluhr/yii2-json-attribute-behavior "*"
或添加
"eluhr/yii2-json-attribute-behavior": "*"
到您的 composer.json 文件的 require 部分。
在 yii\base\Model 或其派生类中,行为的使用方式如下
public function behaviors(): array
{
$behaviors = parent::behaviors();
$behaviors['json-attribute'] = [
'class' => eluhr\jsonAttributeBehavior\JsonAttributeBehavior::class,
'attributes' => [
'data_json'
]
];
return $behaviors;
}
通过使用此行为,属性是字符串还是数组并不重要。此行为始终确保在将数据保存到数据库之前,属性是一个数组,yii 将处理其余工作。
此行为支持 i18n。通过在您的配置中添加 json-attribute-behavior 类别,您可以覆盖默认的错误消息。
通过 composer 安装依赖项后,您可以使用以下命令运行测试
make test
]]>IP2Proxy Yii 扩展允许用户查询一个 IP 地址,以确定它是否被用作开放代理、Web 代理、VPN 匿名器和 TOR 出口节点、搜索引擎机器人、数据中心范围、住宅代理、消费者隐私网络和企业私有网络。它从 IP2Proxy BIN 数据文件或 Web 服务中查找代理 IP 地址。开发人员可以使用 API 查询所有 IP2Proxy BIN 数据库或 Web 服务,以用于使用 Yii 编写的应用程序。
对于 Yii2
php composer.phar require ip2location/ip2proxy-yii 将插件下载到 Yii2 框架中。注意:BIN 数据库指的是以 .BIN 扩展名结尾的二进制文件,而不是 CSV 格式。请选择正确的包进行下载。
use IP2ProxyYii\IP2Proxy_Yii;
// (required) Define IP2Proxy database path.
define('IP2PROXY_DATABASE', '/path/to/ip2proxy/database');
// (required) Define IP2Location.io API key.
define('IP2LOCATION_IO_API_KEY', 'your_api_key');
// (optional) Define Translation information. Refer to https://www.ip2location.io/ip2location-documentation for available languages.
define('IP2LOCATION_IO_LANGUAGE', 'en');
$IP2Proxy = new IP2Proxy_Yii();
$record = $IP2Proxy->get('1.0.241.135');
echo 'Result from BIN Database:<br>';
echo '<p><strong>IP Address: </strong>' . $record['ipAddress'] . '</p>';
echo '<p><strong>IP Number: </strong>' . $record['ipNumber'] . '</p>';
echo '<p><strong>IP Version: </strong>' . $record['ipVersion'] . '</p>';
echo '<p><strong>Country Code: </strong>' . $record['countryCode'] . '</p>';
echo '<p><strong>Country: </strong>' . $record['countryName'] . '</p>';
echo '<p><strong>State: </strong>' . $record['regionName'] . '</p>';
echo '<p><strong>City: </strong>' . $record['cityName'] . '</p>';
echo '<p><strong>Proxy Type: </strong>' . $record['proxyType'] . '</p>';
echo '<p><strong>Is Proxy: </strong>' . $record['isProxy'] . '</p>';
echo '<p><strong>ISP: </strong>' . $record['isp'] . '</p>';
echo '<p><strong>Domain: </strong>' . $record['domain'] . '</p>';
echo '<p><strong>Usage Type: </strong>' . $record['usageType'] . '</p>';
echo '<p><strong>ASN: </strong>' . $record['asn'] . '</p>';
echo '<p><strong>AS: </strong>' . $record['as'] . '</p>';
echo '<p><strong>Last Seen: </strong>' . $record['lastSeen'] . '</p>';
echo '<p><strong>Threat: </strong>' . $record['threat'] . '</p>';
echo '<p><strong>Provider: </strong>' . $record['provider'] . '</p>';
$record = $IP2Proxy->getWebService('1.0.241.135');
echo 'Result from Web service:<br>';
echo '<pre>';
print_r ($record);
echo '</pre>';
此库需要 IP2Proxy BIN 或 IP2Proxy API 密钥数据文件才能正常运行。您可以在以下位置下载 BIN 数据文件:
您还可以注册 IP2Location.io IP 地理位置 API 以获取一个免费的 API 密钥。
电子邮件:support@ip2location.com
]]>IP2Location Yii 扩展允许用户使用 IP2Location 数据库从 IP 地址中查找国家、地区、城市、坐标、邮政编码、时区、ISP、域名、连接类型、区号、天气、MCC、MNC、移动品牌名称、海拔、使用类型、IP 地址类型和 IAB 广告类别。它针对速度和内存使用率进行了优化。开发人员可以使用 API 查询所有 IP2Location BIN 数据库或 Web 服务,以用于使用 Yii 编写的应用程序
对于 Yii2
composer require ip2location/ip2location-yii 将扩展下载到 Yii2 框架中。注意:BIN 数据库指的是以 .BIN 扩展名结尾的二进制文件,而不是 CSV 格式。请选择正确的包进行下载。
use IP2LocationYii\IP2Location_Yii;
// (required) Define IP2Location database path.
define('IP2LOCATION_DATABASE', '/path/to/ip2location/database');
// (required) Define IP2Location.io API key.
define('IP2LOCATION_IO_API_KEY', 'your_api_key');
// (optional) Define Translation information. Refer to https://www.ip2location.io/ip2location-documentation for available languages.
define('IP2LOCATION_IO_LANGUAGE', 'en');
// (optional) Define Translation information. Refer to https://www.ip2location.com/web-service/ip2location for available languages.
define('IP2LOCATION_LANGUAGE', 'en');
$IP2Location = new IP2Location_Yii();
$record = $IP2Location->get('8.8.8.8');
echo 'Result from BIN Database:<br>';
echo 'IP Address: ' . $record['ipAddress'] . '<br>';
echo 'IP Number: ' . $record['ipNumber'] . '<br>';
echo 'ISO Country Code: ' . $record['countryCode'] . '<br>';
echo 'Country Name: ' . $record['countryName'] . '<br>';
echo 'Region Name: ' . $record['regionName'] . '<br>';
echo 'City Name: ' . $record['cityName'] . '<br>';
echo 'Latitude: ' . $record['latitude'] . '<br>';
echo 'Longitude: ' . $record['longitude'] . '<br>';
echo 'ZIP Code: ' . $record['zipCode'] . '<br>';
echo 'Time Zone: ' . $record['timeZone'] . '<br>';
echo 'ISP Name: ' . $record['isp'] . '<br>';
echo 'Domain Name: ' . $record['domainName'] . '<br>';
echo 'Net Speed: ' . $record['netSpeed'] . '<br>';
echo 'IDD Code: ' . $record['iddCode'] . '<br>';
echo 'Area Code: ' . $record['areaCode'] . '<br>';
echo 'Weather Station Code: ' . $record['weatherStationCode'] . '<br>';
echo 'Weather Station Name: ' . $record['weatherStationName'] . '<br>';
echo 'MCC: ' . $record['mcc'] . '<br>';
echo 'MNC: ' . $record['mnc'] . '<br>';
echo 'Mobile Carrier Name: ' . $record['mobileCarrierName'] . '<br>';
echo 'Elevation: ' . $record['elevation'] . '<br>';
echo 'Usage Type: ' . $record['usageType'] . '<br>';
echo 'Address Type: ' . $record['addressType'] . '<br>';
echo 'Category: ' . $record['category'] . '<br>';
$record = $IP2Location->getWebService('8.8.8.8');
echo 'Result from Web service:<br>';
echo '<pre>';
print_r ($record);
echo '</pre>';
此库需要 IP2Location BIN 数据文件或 IP2Location API 密钥才能运行。您可以从以下位置下载 BIN 数据文件:
您还可以注册 IP2Location.io IP 地理位置 API 以获取一个免费的 API 密钥。
电子邮件:support@ip2location.com
]]>使用以下 CSS 样式让轮播按预期工作。
.product_img_slide {
padding: 100px 0 0 0;
}
.product_img_slide > .carousel-inner > .carousel-item {
overflow: hidden;
max-height: 650px;
}
.carousel-inner {
position: relative;
width: 100%;
}
.product_img_slide > .carousel-indicators {
top: 0;
left: 0;
right: 0;
width: 100%;
bottom: auto;
margin: auto;
font-size: 0;
cursor: e-resize;
/* overflow-x: auto; */
text-align: left;
padding: 10px 5px;
/* overflow-y: hidden;*/
white-space: nowrap;
position: absolute;
}
.product_img_slide > .carousel-indicators li {
padding: 0;
width: 76px;
height: 76px;
margin: 0 5px;
text-indent: 0;
cursor: pointer;
background: transparent;
border: 3px solid #333331;
-webkit-border-radius: 0;
border-radius: 0;
-webkit-transition: all 0.7s cubic-bezier(0.22, 0.81, 0.01, 0.99);
transition: all 1s cubic-bezier(0.22, 0.81, 0.01, 0.99);
}
.product_img_slide > .carousel-indicators .active {
width: 76px;
border: 0;
height: 76px;
margin: 0 5px;
background: transparent;
border: 3px solid #c13c3d;
}
.product_img_slide > .carousel-indicators > li > img {
display: block;
/*width:114px;*/
height: 76px;
}
.product_img_slide .carousel-inner > .carousel-item > a > img, .carousel-inner > .carousel-item > img, .img-responsive, .thumbnail a > img, .thumbnail > img {
display: block;
max-width: 100%;
line-height: 1;
margin: auto;
}
.product_img_slide .carousel-control-prev {
top: 58%;
/*left: auto;*/
right: 76px;
opacity: 1;
width: 50px;
bottom: auto;
height: 50px;
font-size: 50px;
cursor: pointer;
font-weight: 700;
overflow: hidden;
line-height: 50px;
text-shadow: none;
text-align: center;
position: absolute;
background: transparent;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.6);
-webkit-box-shadow: none;
box-shadow: none;
-webkit-border-radius: 0;
border-radius: 0;
-webkit-transition: all 0.6s cubic-bezier(0.22, 0.81, 0.01, 0.99);
transition: all 0.6s cubic-bezier(0.22, 0.81, 0.01, 0.99);
}
.product_img_slide .carousel-control-next {
top: 58%;
left: auto;
right: 25px;
opacity: 1;
width: 50px;
bottom: auto;
height: 50px;
font-size: 50px;
cursor: pointer;
font-weight: 700;
overflow: hidden;
line-height: 50px;
text-shadow: none;
text-align: center;
position: absolute;
background: transparent;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.6);
-webkit-box-shadow: none;
box-shadow: none;
-webkit-border-radius: 0;
border-radius: 0;
-webkit-transition: all 0.6s cubic-bezier(0.22, 0.81, 0.01, 0.99);
transition: all 0.6s cubic-bezier(0.22, 0.81, 0.01, 0.99);
}
.product_img_slide .carousel-control-next:hover, .product_img_slide .carousel-control-prev:hover {
color: #c13c3d;
background: transparent;
}
这是一个轮播小部件,它是 yii\bootstrap5\Carousel 的扩展,用于显示图片缩略图作为轮播的指示器。
这是小部件代码。
<?php
namespace app\widgets;
use Yii;
use yii\bootstrap5\Html;
class Carousel extends \yii\bootstrap5\Carousel
{
public $thumbnails = [];
public function init()
{
parent::init();
Html::addCssClass($this->options, ['data-bs-ride' => 'carousel']);
if ($this->crossfade) {
Html::addCssClass($this->options, ['animation' => 'carousel-fade']);
}
}
public function renderIndicators(): string
{
if ($this->showIndicators === false){
return '';
}
$indicators = [];
for ($i = 0, $count = count($this->items); $i < $count; $i++){
$options = [
'data' => [
'bs-target' => '#' . $this->options['id'],
'bs-slide-to' => $i
],
'type' => 'button',
'thumb' => $this->thumbnails[$i]['thumb']
];
if ($i === 0){
Html::addCssClass($options, ['activate' => 'active']);
$options['aria']['current'] = 'true';
}
$indicators[] = Html::tag('li',Html::img($options['thumb']), $options);
}
return Html::tag('ol', implode("\n", $indicators), ['class' => ['carousel-indicators']]);
} }
您可以在您的视图文件中使用以上小部件,如下所示
<?php
$indicators = [
'0' =>[ 'thumb' => "https://placehold.co/150X150?text=A"],
'1' => ['thumb' => 'https://placehold.co/150X150?text=B'],
'2' => [ 'thumb' => 'https://placehold.co/150X150?text=C']
];
$items = [
[ 'content' =>Html::img('https://live.staticflickr.com/8333/8417172316_c44629715e_w.jpg')],
[ 'content' =>Html::img('https://live.staticflickr.com/3812/9428789546_3a6ba98c49_w.jpg')],
[ 'content' =>Html::img('https://live.staticflickr.com/8514/8468174902_a8b505a063_w.jpg')]
];
echo Carousel::widget([
'items' =>
$items,
'thumbnails' => $indicators,
'options' => [
'data-interval' => 3, 'data-bs-ride' => 'scroll','class' => 'carousel product_img_slide',
],
]);
]]>Yii 附带了国际化 (i18n)“开箱即用”。手册中有关于如何配置 Yii 使用 i18n 的说明,但关于如何将 i18n 完全集成到引导菜单中却很少有信息。本文档旨在解决这一问题。

GitHub 存储库还包含语言标志、一些国家标志、语言代码列表及其语言名称以及 Yii“开箱即用”识别的语言列表。YouTube 上很快就会发布一个视频。
确保您的系统已设置为使用 i18n。来自 Yii2 手册
Yii 使用
PHP intl扩展来提供其大部分 I18N 功能,例如yii\i18n\Formatter类的日期和数字格式以及使用yii\i18n\MessageFormatter的消息格式。这两个类在未安装 intl 扩展时提供回退机制。但是,回退实现仅适用于英语目标语言。因此,强烈建议您在需要 I18N 时安装intl。
首先,您需要创建一个配置文件。
决定将配置文件存储在何处(例如,在./messages/目录中,文件名create_i18n.php)。在项目中创建该目录,然后从项目的根目录在终端(Windows:CMD)中执行以下命令
./yii message/config-template ./messages/create_i18n.php
或者,为了更细致的控制
./yii message/config --languages=en-US --sourcePath=@app --messagePath=messages ./messages/create_i18n.php
在新建的文件中,更改(或创建)要翻译的语言数组
// array, required, list of language codes that the extracted messages
// should be translated to. For example, ['zh-CN', 'de'].
'languages' => [
'en-US',
'fr',
'pt'
],
如果需要,更改create_i18n.php中的根目录以指向 messages 目录 - 默认值为messages。请注意,如果以上文件位于 messages 目录中(建议),则不要更改此'messagePath' => __DIR__,。如果您将messages的目录更改为,例如,/config/(不建议),您可以使用以下命令
// Root directory containing message translations.
'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . 'config',
编辑完您需要的语言后,创建的文件应该类似于以下内容
<?php
return [
// string, required, root directory of all source files
'sourcePath' => __DIR__ . DIRECTORY_SEPARATOR . '..',
// array, required, list of language codes (in alphabetical order) that the extracted messages
// should be translated to. For example, ['zh-CN', 'de'].
'languages' => [
// to localise a particular language use the language code followed by the dialect in CAPS
'en-US', // USA English
'es',
'fr',
'it',
'pt',
],
/* 'languages' => [
'af', 'ar', 'az', 'be', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'el', 'es', 'et', 'fa', 'fi', 'fr', 'he', 'hi',
'pt-BR', 'ro', 'hr', 'hu', 'hy', 'id', 'it', 'ja', 'ka', 'kk', 'ko', 'kz', 'lt', 'lv', 'ms', 'nb-NO', 'nl',
'pl', 'pt', 'ru', 'sk', 'sl', 'sr', 'sr-Latn', 'sv', 'tg', 'th', 'tr', 'uk', 'uz', 'uz-Cy', 'vi', 'zh-CN',
'zh-TW'
], */
// string, the name of the function for translating messages.
// Defaults to 'Yii::t'. This is used as a mark to find the messages to be
// translated. You may use a string for single function name or an array for
// multiple function names.
'translator' => ['\Yii::t', 'Yii::t'],
// boolean, whether to sort messages by keys when merging new messages
// with the existing ones. Defaults to false, which means the new (untranslated)
// messages will be separated from the old (translated) ones.
'sort' => false,
// boolean, whether to remove messages that no longer appear in the source code.
// Defaults to false, which means these messages will NOT be removed.
'removeUnused' => false,
// boolean, whether to mark messages that no longer appear in the source code.
// Defaults to true, which means each of these messages will be enclosed with a pair of '@@' marks.
'markUnused' => true,
// array, list of patterns that specify which files (not directories) should be processed.
// If empty or not set, all files will be processed.
// See helpers/FileHelper::findFiles() for pattern matching rules.
// If a file/directory matches both a pattern in "only" and "except", it will NOT be processed.
'only' => ['*.php'],
// array, list of patterns that specify which files/directories should NOT be processed.
// If empty or not set, all files/directories will be processed.
// See helpers/FileHelper::findFiles() for pattern matching rules.
// If a file/directory matches both a pattern in "only" and "except", it will NOT be processed.
'except' => [
'.*',
'/.*',
'/messages',
'/migrations',
'/tests',
'/runtime',
'/vendor',
'/BaseYii.php',
],
// 'php' output format is for saving messages to php files.
'format' => 'php',
// Root directory containing message translations.
'messagePath' => __DIR__,
// boolean, whether the message file should be overwritten with the merged messages
'overwrite' => true,
/*
// File header used in generated messages files
'phpFileHeader' => '',
// PHPDoc used for array of messages with generated messages files
'phpDocBlock' => null,
*/
/*
// Message categories to ignore
'ignoreCategories' => [
'yii',
],
*/
/*
// 'db' output format is for saving messages to database.
'format' => 'db',
// Connection component to use. Optional.
'db' => 'db',
// Custom source message table. Optional.
// 'sourceMessageTable' => '{{%source_message}}',
// Custom name for translation message table. Optional.
// 'messageTable' => '{{%message}}',
*/
/*
// 'po' output format is for saving messages to gettext po files.
'format' => 'po',
// Root directory containing message translations.
'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . 'messages',
// Name of the file that will be used for translations.
'catalog' => 'messages',
// boolean, whether the message file should be overwritten with the merged messages
'overwrite' => true,
*/
];
/config/web.php文件 ¶在web.php文件中,在'id' => 'basic',下方添加
'language' => 'en',
'sourceLanguage' => 'en',
注意:您应始终使用'sourceLanguage' => 'en',因为通常从英语翻译成其他语言更简单且更便宜。如果没有设置sourceLanguage,则默认为'en'。
将以下内容添加到'components' => [...]部分
'i18n' => [
'translations' => [
'app*' => [
'class' => 'yii\i18n\PhpMessageSource', // Using text files (usually faster) for the translations
//'basePath' => '@app/messages', // Uncomment and change this if your folder is not called 'messages'
'sourceLanguage' => 'en',
'fileMap' => [
'app' => 'app.php',
'app/error' => 'error.php',
],
// Comment out in production version
// 'on missingTranslation' => ['app\components\TranslationEventHandler', 'handleMissingTranslation'],
],
],
],
现在告诉 Yii 您要在视图文件中翻译哪些文本。这是通过在代码中添加Yii::t('app', '要翻译的文本')来完成的。
例如,在/views/layouts/main.php中,更改菜单标签,如下所示
'items' => [
// ['label' => 'Home', 'url' => ['/site/index']], // Orignal code
['label' => Yii::t('app', 'Home'), 'url' => ['/site/index']],
['label' => Yii::t('app', 'About'), 'url' => ['/site/about']],
['label' => Yii::t('app', 'Contact'), 'url' => ['/site/contact']],
Yii::$app->user->isGuest ? ['label' => Yii::t('app', 'Login'), 'url' => ['/site/login']] : '<li class="nav-item">'
. Html::beginForm(['/site/logout'])
. Html::submitButton(
// 'Logout (' . Yii::$app->user->identity->username . ')', // change this line as well to the following:
Yii::t('app', 'Logout ({username})'), ['username' => Yii::$app->user->identity->username]),
['class' => 'nav-link btn btn-link logout']
)
. Html::endForm()
. '</li>',
],
要创建翻译文件,请从项目的根目录在终端中运行以下命令
./yii message ./messages/create_i18n.php
现在,获取已翻译的消息。例如,在法语/messages/fr/app.php中
'Home' => 'Accueil',
'About' => 'À propos',
...
这需要几个步骤。
每种语言都需要一个key和一个name。
key是 ICU 语言代码 ISO 639.1(小写)(可选的国家/地区代码 ISO 3166 大写)例如
法语:
fr或 法语加拿大:fr-CA葡萄牙语:
pt或 葡萄牙语巴西:pt-BR
name是该语言的名称(用该语言表示)。例如,法语:'Français',日语:'日本の'。这一点很重要,因为用户可能不理解浏览器的当前语言。
在/config/params.php中,创建一个名为languages的数组,其中包含所需的语言。例如
/* List of languages and their codes
*
* format:
* 'Language Code' => 'Language Name',
* e.g.
* 'fr' => 'Français',
*
* please use alphabetical order of language code
* Use the language name in the "user's" Language
* e.g.
* 'ja' => '日本の',
*/
'languages' => [
// 'da' => 'Danske',
// 'de' => 'Deutsche',
// 'en' => 'English', // NOT REQUIRED the sourceLanguage (i.e. the default)
'en-GB' => 'British English',
'en-US' => 'American English',
'es' => 'Español',
'fr' => 'Français',
'it' => 'Italiano',
// 'ja' => '日本の', // Japanese with the word "Japanese" in Kanji
// 'nl' => 'Nederlandse',
// 'no' => 'Norsk',
// 'pl' => 'Polski',
'pt' => 'Português',
// 'ru' => 'Русский',
// 'sw' => 'Svensk',
// 'zh' => '中国的',
],
在/controllers/SiteController.php(默认控制器)中,添加一个名为actionLanguage()的“操作”。此“操作”更改语言并设置 cookie,以便浏览器“记住”页面的语言请求以及对该站点的回访。
/**
* Called by the ajax handler to change the language and
* Sets a cookie based on the language selected
*
*/
public function actionLanguage()
{
$lang = Yii::$app->request->post('lang');
// If the language "key" is not NULL and exists in the languages array in params.php, change the language and set the cookie
if ($lang !== NULL && array_key_exists($lang, Yii::$app->params['languages']))
{
$expire = time() + (60 * 60 * 24 * 365); // 1 year - alter accordingly
Yii::$app->language = $lang;
$cookie = new yii\web\Cookie([
'name' => 'lang',
'value' => $lang,
'expire' => $expire,
]);
Yii::$app->getResponse()->getCookies()->add($cookie);
}
Yii::$app->end();
}
请记住将方法设置为POST。在behaviors()中,在actions下方,设置'language' => ['post'],,如下所示
'verbs' => [
'class' => VerbFilter::class,
'actions' => [
'logout' => ['post'],
'language' => ['post'],
],
],
确保为每个请求提供正确的语言。
在/components/目录中,创建一个名为LanguageHandler.php的文件,并将以下代码添加到其中
<?php
/*
* Copyright ©2023 JQL all rights reserved.
* http://www.jql.co.uk
*/
/*
Created on : 19-Nov-2023, 13:23:54
Author : John Lavelle
Title : LanguageHandler
*/
namespace app\components;
use yii\helpers\Html;
class LanguageHandler extends \yii\base\Behavior
{
public function events()
{
return [\yii\web\Application::EVENT_BEFORE_REQUEST => 'handleBeginRequest'];
}
public function handleBeginRequest($event)
{
if (\Yii::$app->getRequest()->getCookies()->has('lang') && array_key_exists(\Yii::$app->getRequest()->getCookies()->getValue('lang'), \Yii::$app->params['languages']))
{
// Get the language from the cookie if set
\Yii::$app->language = \Yii::$app->getRequest()->getCookies()->getValue('lang');
}
else
{
// Use the browser language - note: some systems use an underscore, if used, change it to a hyphen
\Yii::$app->language = str_replace('_', '-', HTML::encode(locale_accept_from_http($_SERVER['HTTP_ACCEPT_LANGUAGE'])));
}
}
}
/* End of file LanguageHandler.php */
/* Location: ./components/LanguageHandler.php */
/config/web.php调用LanguageHandler.php ¶通过将以下内容添加到'params' => $params,的正上方或正下方,从/config/web.php“调用”LanguageHandler.php文件
// Update the language on selection
'as beforeRequest' => [
'class' => 'app\components\LanguageHandler',
],
/views/layouts/main.php ¶main.php使用 Bootstrap 来创建菜单。需要在菜单中添加一个项目(下拉菜单),以允许用户选择语言。
将use yii\helpers\Url;添加到main.php的“使用”部分。
在echo Nav::widget([...])的正上方添加以下代码
// Get the languages and their keys, also the current route
foreach (Yii::$app->params['languages'] as $key => $language)
{
$items[] = [
'label' => $language, // Language name in it's language - already translated
'url' => Url::to(['site/index']), // Route
'linkOptions' => ['id' => $key, 'class' => 'language'], // The language "key"
];
}
在以下部分中
echo Nav::widget([...])`
位于
'options' => ['class' => 'navbar-nav ms-auto'], // ms-auto aligns the menu right`
和
'items' => [...]
之间,添加
'encodeLabels' => false, // Required to enter HTML into the labels
如下所示
echo Nav::widget([
'options' => ['class' => 'navbar-nav ms-auto'], // ms-auto aligns the menu right
'encodeLabels' => false, // Required to enter HTML into the labels
'items' => [
['label' => Yii::t('app', 'Home'), 'url' => ['/site/index']],
...
现在添加下拉菜单。它可以放置在'items' => [...]中的任何位置。
// Dropdown Nav Menu: https://yiiframework.cn/doc/api/2.0/yii-widgets-menu
[
'label' => Yii::t('app', 'Language')),
'url' => ['#'],
'options' => ['class' => 'language', 'id' => 'languageTop'],
'encodeLabels' => false, // Optional but required to enter HTML into the labels for images
'items' => $items, // add the languages into the Dropdown
],
main.php中用于导航栏的代码应该类似于以下内容
NavBar::begin([
'brandLabel' => Yii::$app->name, // set in /config/web.php
'brandUrl' => Yii::$app->homeUrl,
'options' => ['class' => 'navbar-expand-md navbar-dark bg-dark fixed-top']
]);
// Get the languages and their keys, also the current route
foreach (Yii::$app->params['languages'] as $key => $language)
{
$items[] = [
'label' => $language, // Language name in it's language
'url' => Url::to(['site/index']), // Current route so the page refreshes
'linkOptions' => ['id' => $key, 'class' => 'language'], // The language key
];
}
echo Nav::widget([
'options' => ['class' => 'navbar-nav ms-auto'], // ms-auto aligns the menu right
'encodeLabels' => false, // Required to enter HTML into the labels
'items' => [
['label' => Yii::t('app', 'Home'), 'url' => ['/site/index']],
['label' => Yii::t('app', 'About'), 'url' => ['/site/about']],
['label' => Yii::t('app', 'Contact'), 'url' => ['/site/contact']],
// Dropdown Nav Menu: https://yiiframework.cn/doc/api/2.0/yii-widgets-menu
[
'label' => Yii::t('app', 'Language') ,
'url' => ['#'],
'options' => ['class' => 'language', 'id' => 'languageTop'],
'encodeLabels' => false, // Required to enter HTML into the labels
'items' => $items, // add the languages into the Dropdown
],
Yii::$app->user->isGuest ? ['label' => Yii::t('app', 'Login'), 'url' => ['/site/login']] : '<li class="nav-item">'
. Html::beginForm(['/site/logout'])
. Html::submitButton(
// 'Logout (' . Yii::$app->user->identity->username . ')',
Yii::t('app', 'Logout ({username})', ['username' => Yii::$app->user->identity->username]),
['class' => 'nav-link btn btn-link logout']
)
. Html::endForm()
. '</li>',
],
]);
NavBar::end();
如果需要在语言名称旁边添加语言标志或图片,请参见本文档末尾的“可选项目”。
要调用语言操作actionLanguage(),请在 JavaScript 文件中进行 Ajax 调用。
在/web/js/中创建一个名为language.js的文件。
将以下代码添加到文件中
/*
* Copyright ©2023 JQL all rights reserved.
* http://www.jql.co.uk
*/
/**
* Set the language
*
* @returns {undefined}
*/
$(function () {
$(document).on('click', '.language', function (event) {
event.preventDefault();
let lang = $(this).attr('id'); // Get the language key
/* if not the top level, set the language and reload the page */
if (lang !== 'languageTop') {
$.post(document.location.origin + '/site/language', {'lang': lang}, function (data) {
location.reload(true);
});
}
});
});
要将 JavaScript 文件添加到资产中,请更改项目目录中的/assets/AppAsset.php。在public $js = []中添加'js/language.js',如下所示
public $js = [
'js/language.js',
];
国际化现在应该在您的项目中生效。
以下内容是可选的,但可能对您和/或用户有所帮助。
Yii 可以检查是否为Yii::t('app', '要翻译的文本')块中的特定文本存在翻译。
有两个步骤
A. 在/config/web.php中取消注释以下行
// 'on missingTranslation' => ['app\components\TranslationEventHandler', 'handleMissingTranslation'],
B. 创建一个 TranslationEventHandler
在/components/中创建一个名为TranslationEventHandler.php的文件,并将以下代码添加到其中
<?php
/**
* TranslationEventHandler
*
* @copyright © 2023, John Lavelle Created on : 14 Nov 2023, 16:05:32
*
*
* Author : John Lavelle
* Title : TranslationEventHandler
*/
// Change the Namespace (app, frontend, backend, console etc.) if necessary (default in Yii Basic is "app").
namespace app\components;
use yii\i18n\MissingTranslationEvent;
/**
* TranslationEventHandler
*
*
* @author John Lavelle
* @since 1.0 // Update version number
*/
class TranslationEventHandler
{
/**
* Adds a message to missing translations in Development Environment only
*
* @param MissingTranslationEvent $event
*/
public static function handleMissingTranslation(MissingTranslationEvent $event)
{
// Only check in the development environment
if (YII_ENV_DEV)
{
$event->translatedMessage = "@MISSING: {$event->category}.{$event->message} FOR LANGUAGE {$event->language} @";
}
}
}
如果存在缺少的翻译,则文本将替换为类似于以下文本的消息
@MISSING: app.Logout (John) FOR LANGUAGE fr @
这里 Yii 发现没有法语翻译
Yii::t('app', 'Logout ({username})', ['username' => Yii::$app->user->identity->username]),
这非常有用且推荐使用,因为它可以帮助用户找到正确的语言。这需要几个步骤。
a. 创建标志图片。
图片的宽度应为 25 像素,高度应为 15 像素。图片必须与 params.php 中语言数组中的语言键具有相同的名称。例如:fr.png或en-US.png。如果图片不是“.png”类型,请将下面部分b.中的代码更改为正确的文件扩展名。
将图片放在/web/images/flags/目录中。
b. 更改/views/layouts/main.php中的代码,使“导航栏”的代码如下所示
<header id="header">
<?php
NavBar::begin([
'brandLabel' => Yii::$app->name,
'brandUrl' => Yii::$app->homeUrl,
'options' => ['class' => 'navbar-expand-md navbar-dark bg-dark fixed-top']
]);
// Get the languages and their keys, also the current route
foreach (Yii::$app->params['languages'] as $key => $language)
{
$items[] = [
// Display the image before the language name
'label' => Html::img('/images/flags/' . $key . '.png', ['alt' => 'flag ' . $language, 'class' => 'inline-block align-middle', 'title' => $language,]) . ' ' . $language, // Language name in it's language
'url' => Url::to(['site/index']), // Route
'linkOptions' => ['id' => $key, 'class' => 'language'], // The language key
];
}
echo Nav::widget([
'options' => ['class' => 'navbar-nav ms-auto'], // ms-auto aligns the menu right
'encodeLabels' => false, // Required to enter HTML into the labels
'items' => [
['label' => Yii::t('app', 'Home'), 'url' => ['/site/index']],
['label' => Yii::t('app', 'About'), 'url' => ['/site/about']],
['label' => Yii::t('app', 'Contact'), 'url' => ['/site/contact']],
// Dropdown Nav Menu: https://yiiframework.cn/doc/api/2.0/yii-widgets-menu
[
// Display the current language "flag" after the Dropdown title (before the caret)
'label' => Yii::t('app', 'Language') . ' ' . Html::img('@web/images/flags/' . Yii::$app->language . '.png', ['class' => 'inline-block align-middle', 'title' => Yii::$app->language]),
'url' => ['#'],
'options' => ['class' => 'language', 'id' => 'languageTop'],
'encodeLabels' => false, // Required to enter HTML into the labels
'items' => $items, // add the languages into the Dropdown
],
Yii::$app->user->isGuest ? ['label' => Yii::t('app', 'Login'), 'url' => ['/site/login']] : '<li class="nav-item">'
. Html::beginForm(['/site/logout'])
. Html::submitButton(
// 'Logout (' . Yii::$app->user->identity->username . ')',
Yii::t('app', 'Logout ({username})', ['username' => Yii::$app->user->identity->username]),
['class' => 'nav-link btn btn-link logout']
)
. Html::endForm()
. '</li>',
],
]);
NavBar::end();
?>
</header>
就这样!享受吧…
要了解更多信息,请参阅
如果您使用此代码,请按如下方式说明
国际化 (i18n) 菜单代码由 JQL 提供,https://visualaccounts.co.uk ©2023 JQL
许可证 (BSD-3-Clause 许可证)
版权声明
国际化 (i18n) 菜单代码由 JQL 提供,https://visualaccounts.co.uk ©2023 JQL 保留所有权利
在满足以下条件的情况下,允许以源代码和二进制形式重新分发和使用此软件,无论是否修改。
源代码的再分发必须保留上述版权声明、此条件列表和以下免责声明。
二进制形式的再分发必须在随分发提供的文档和/或其他材料中复制上述版权声明、此条件列表和以下免责声明。
未经事先明确书面许可,不得使用 John Lavelle、JQL、Visual Accounts 或其贡献者的名称来认可或宣传源于此软件的产品。
"所有 JQL 代码和软件(包括全球万维网页面以及其作者的页面)均按“原样”提供,不附带任何形式的保证。在法律允许的最大范围内,作者和出版商及其代理商明确声明不承担任何明示或暗示的保证,包括但不限于对适销性和特定用途适用性的暗示保证。对于代码,作者和出版商及其代理商对因使用代码直接或间接产生的任何损失或损害不承担任何责任,即使作者和/或出版商及其代理商已收到有关此类损害的可能性通知。在不限制上述内容的情况下,作者和出版商及其代理商对因使用代码造成的任何利润损失、业务中断、设备或数据损坏、运营中断或任何其他商业损害(包括但不限于直接、间接、特殊、附带、后果性或其他损害)不承担任何责任。"
]]>这是一个使用 yii2 基础应用程序模板创建的应用程序模板,用于演示我的扩展 slideradmin 的用法。
有多种方法可以创建验证器,但这里我们使用正则表达式或 JavaScript 正则表达式或 RegExp 来创建验证器。在本文中,我们将看到最常用的表达式。
步骤 1: 创建一个新的验证器类,如下所示或 Validator
查看第一个示例 10 位手机号码验证。
<?php
namespace common\validators;
use yii\validators\Validator;
class MobileValidator extends Validator {
public function validateAttribute($model, $attribute) {
if (isset($model->$attribute) and $model->$attribute != '') {
if (!preg_match('/^[123456789]\d{9}$/', $model->$attribute)) {
$this->addError($model, $attribute, 'In Valid Mobile / Phone number');
}
}
}
}
这里我们可以根据需要编写不同的正则表达式 `php preg_match('/^[123456789]\d{9}$/', $model->$attribute) `
步骤 2: 如何使用验证器
我希望每个人都知道如何使用验证器,但这里有一个关于如何使用它的示例。
在您的模型类中添加一个新的规则,如下所示 `php [['mobile'],\common\validators\MobileValidator::class], [['mobile'], 'string', 'max' => 10],
So It's Very Simple to use a Custom Validator.
As I Told you Earlier that i show you some more Example for Using Regular Expression Validator Just Replace these string in preg_match.
1. Aadhar Number Validator
```php
preg_match('/^[2-9]{1}[0-9]{3}[0-9]{4}[0-9]{4}$/', $model->$attribute)
银行账户号码验证器 `php preg_match("/^[0-9]{9,18}+$/", $model->$attribute) `
银行 IFSC 代码验证器 `php preg_match("/^[A-Z]{4}0[A-Z0-9]{6}$/", $model->$attribute) `
身份证号码验证器 `php preg_match('/^([a-zA-Z]){5}([0-9]){4}([a-zA-Z]){1}?$/', $model->$attribute) `
邮政编码验证器 `php preg_match('/^[0-9]{6}+$/', $model->$attribute) `
GSTIN 验证器 `php preg_match("/^([0][1-9]|[1-2][0-9]|[3][0-5])([a-zA-Z]{5}[0-9]{4}[a-zA-Z]{1}[1-9a-zA-Z]{1}[zZ]{1}[0-9a-zA-Z]{1})+$/", $model->$attribute) `
这是其他类型的自定义验证器。
<?php
namespace common\validators;
use yii\validators\Validator;
/**
* Class Word500Validator
* @author Aayush Saini <aayushsaini9999@gmail.com>
*/
class Word500Validator extends Validator
{
public function validateAttribute($model, $attribute)
{
if ($model->$attribute != '') {
if (str_word_count($model->$attribute) > 500) {
$this->addError($model, $attribute, $model->getAttributeLabel($attribute) . ' length can not exceeded 500 words.');
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
return $model->errors;
}
}
}
}
现在我假设您在阅读完这篇文章后可以根据您的需要创建任何类型的验证器。
:) 感谢阅读
]]>GridView 在页脚显示列的总和 `PHP use yii\grid\DataColumn;
/**
@author shiv / class TSumColumn extends DataColumn { public function getDataCellValue($model, $key, $index) {
$value = parent::getDataCellValue($model, $key, $index);
if ( is_numeric($value))
{
$this->footer += $value;
}
return $value;
}
}
`
现在您必须在 GridView 中启用页脚。
echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'showFooter' => true,
还要更改列类。
[
'class' => TSumColumn::class,
'attribute' => 'amount'
],
您将在网格的页脚中看到总计。如果需要,可以将其应用于多个列。
]]>我有一个调用,它可以帮助我直接在 HTML 表格中显示 json。
Json2Table::formatContent($json);
Json2Table 类的代码如下:
============================================
/**
* Class convert Json to html table. It help view json data directly.
* @author shiv
*
*/
class Json2Table
{
public static function formatContent($content, $class = 'table table-bordered')
{
$html = "";
if ($content != null) {
$arr = json_decode(strip_tags($content), true);
if ($arr && is_array($arr)) {
$html .= self::arrayToHtmlTableRecursive($arr, $class);
}
}
return $html;
}
public static function arrayToHtmlTableRecursive($arr, $class = 'table table-bordered')
{
$str = "<table class='$class'><tbody>";
foreach ($arr as $key => $val) {
$str .= "<tr>";
$str .= "<td>$key</td>";
$str .= "<td>";
if (is_array($val)) {
if (! empty($val)) {
$str .= self::arrayToHtmlTableRecursive($val, $class);
}
} else {
$val = nl2br($val);
$str .= "<strong>$val</strong>";
}
$str .= "</td></tr>";
}
$str .= "</tbody></table>";
return $str;
}
}
]]>在印度有身份证号码,我们可能需要对输入的身份证号码进行验证。所以我为 yii2 创建了一个验证器。
use yii\validators\Validator;
class TAadharNumberValidator extends Validator
{
public $regExPattern = '/^\d{4}\s\d{4}\s\d{4}$/';
public function validateAttribute($model, $attribute)
{
if (preg_match($this->regExPattern, $model->$attribute)) {
$model->addError($attribute, 'Not valid Aadhar Card Number');
}
}
}
]]>大家好,在这篇文章中,我分享了自己在 YII2 面试中最常被问到的问题。
这些是最常见的面试官可能会问您的问题,如果您要去面试。
如果有人有其他问题,请在评论中分享!
]]>我的一个 网站 充斥着垃圾邮件机器人,因此 Gmail 给我的邮件域名打了一个差评,我再也无法向@gmail 地址发送电子邮件了,无论是从我的电子邮件、我的系统还是我托管的其他域名和网站。
我确实从我的一个网站中删除了所有垃圾邮件机器人的活动,通过 Gmail 支持论坛申诉了该决定,但仍然,我被阻止与我的客户联系,他们的邮箱位于@gmail.com,而且似乎无法将域名分数更改回原来的水平。
已经快两个星期了,我的域名分数仍然在 https://postmaster.google.com/ 上保持差评。
感谢 @Google :(
因此,我不得不找到一种方法来向我的客户发送购买、许可证过期和其他通知。
我使用的是 PHP Yii2 框架,事实证明,这非常容易。
我们需要一个 @gmail.com 帐户来发送通知。有一点很重要。创建帐户后,您需要启用安全性较低应用程序访问选项。

它允许我们通过 Gmail SMTP 服务器发送电子邮件。
在您的 Yii2 框架目录中,修改您的配置文件/common/config/Main.php(我使用的是高级主题)并包含自定义邮件组件(随意命名)
<?php
return [
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
...
'components' => [
'mailerGmail' => [
'class' => 'yii\swiftmailer\Mailer',
'viewPath' => '@common/mail',
'useFileTransport' => false,
'transport' => [
'class' => 'Swift_SmtpTransport',
'host' => 'smtp.gmail.com',
'username' => 'gmail.helper.account',
'password' => 'PUT-YOUR-PASSWORD-HERE',
'port' => '587',
'encryption' => 'tls',
],
],
],
];
我在我的一个组件中添加了一个辅助函数,该组件注册为Yii::$app->Custom。它根据要发送电子邮件的域名的名称返回默认的邮件程序实例。
我还更新了代码以检测电子邮件不包含@gmail.com 字符串,但仍然使用 Gmail MX 服务器来处理电子邮件的情况。
检测是基于使用 PHP 内置函数getmxrr() 检查域名邮件服务器记录,如果失败,我会向 Google DNS 服务 API 发送远程GET 查询以检查MX 记录。
////////////////////////////////////////////////////////////////////////////////
//
// get default mailer depending on the provided email address
//
////////////////////////////////////////////////////////////////////////////////
public function getMailer($email)
{
// detect if the email or domain is using Gmail to send emails
if (Yii::$app->params['forwardGmail'])
{
// detect @gmail.com domain first
if (str_ends_with($email, "@gmail.com"))
{
return Yii::$app->mailerGmail;
}
// extract domain name
$parts = explode('@', $email);
$domain = array_pop($parts);
// check DNS using local server requests to DNS
// if it fails query Google DNS service API (might have limits)
if (getmxrr($domain, $mx_records))
{
foreach($mx_records as $record)
{
if (stripos($record, "google.com") !== false || stripos($record, "googlemail.com") !== false)
{
return Yii::$app->mailerGmail;
}
}
// return default mailer (if there were records detected but NOT google)
return Yii::$app->mailer;
}
// make DNS request
$client = new Client();
$response = $client->createRequest()
->setMethod('GET')
->setUrl('https://dns.google.com/resolve')
->setData(['name' => $domain, 'type' => 'MX'])
->setOptions([
'timeout' => 5, // set timeout to 5 seconds for the case server is not responding
])
->send();
if ($response->isOk)
{
$parser = new JsonParser();
$data = $parser->parse($response);
if ($data && array_key_exists("Answer", $data))
{
foreach ($data["Answer"] as $key => $value)
{
if (array_key_exists("name", $value) && array_key_exists("data", $value))
{
if (stripos($value["name"], $domain) !== false)
{
if (stripos($value["data"], "google.com") !== false || stripos($value["data"], "googlemail.com") !== false)
{
return Yii::$app->mailerGmail;
}
}
}
}
}
}
}
// return default mailer
return Yii::$app->mailer;
}
如果域名以@gmail.com 结尾,或者域名使用的是 Gmail 邮件系统,则使用mailerGmail 实例,否则使用默认邮件组件Yii::$app->mailer。
/**
* Sends an email to the specified email address using the information collected by this model.
*
* @return boolean whether the email was sent
*/
public function sendEmail()
{
// find all active subscribers
$message = Yii::$app->Custom->getMailer($this->email)->compose();
$message->setTo([$this->email => $this->name]);
$message->setFrom([\Yii::$app->params['supportEmail'] => "Bartosz Wójcik"]);
$message->setSubject($this->subject);
$message->setTextBody($this->body);
$headers = $message->getSwiftMessage()->getHeaders();
// message ID header (hide admin panel)
$msgId = $headers->get('Message-ID');
$msgId->setId(md5(time()) . '@pelock.com');
$result = $message->send();
return $result;
}
这只是一个临时解决方案,您需要了解,使用这种方法无法发送批量邮件,Gmail 也会对新邮箱施加一些限制。
如果您的域名落在信誉度差的范围内,似乎没有任何简单的解决办法。我在 Gmail 支持论坛上看到,有些人等待了一个多月才让 Gmail 解封了他们的域名,没有任何结果和回复。我的域名没有列在其他任何被阻止的 RBL 列表(垃圾邮件列表)中,只有 Gmail 阻止了它,但这足以理解 Google 的影响力,它可以在一瞬间摧毁您的业务,而您却没有机会修复它...
]]>JWT 是 JSON Web Token 的缩写。例如,它用于代替会话来维护与 API 交互的浏览器的登录状态——因为浏览器会话容易受到 CSRF 安全问题的攻击。JWT 也比设置 OAuth 身份验证机制更简单。
该概念依赖于两个令牌
此令牌使用 \sizeg\jwt\Jwt::class 生成。它没有存储在服务器端,并在所有后续的 API 请求中通过Authorization 标头发送。那么如何识别用户呢?好吧,JWT 内容包含用户 ID。我们盲目地信任此值。
此令牌仅在登录时生成,并存储在user_refresh_token表中。用户在数据库中可能有多个 RefreshToken。
/auth/login端点登录: ¶在我们的actionLogin()方法中,如果凭据正确,会发生两件事。
httpOnly cookie 发送,仅限于/auth/refresh-token路径。JWT 存储在浏览器的localStorage中,并且从现在开始必须在所有请求中发送。RefreshToken 在您的 cookie 中,但不能通过 Javascript 读取/访问/篡改(因为它 是httpOnly)。
一段时间后,JWT 最终会过期。在这种情况下,您的 API 必须返回401 - Unauthorized。在您的应用程序的 HTTP 客户端(例如 Axios)中,添加一个拦截器,它检测 401 状态,将失败的请求存储在一个队列中,并调用/auth/refresh-token端点。
调用时,此端点将通过 cookie 接收 RefreshToken。然后,您必须在您的表中检查这是否是一个有效的 RefreshToken,关联的用户 ID 是谁,生成一个新的 JWT 并将其作为 JSON 发送回来。
您的 HTTP 客户端必须使用这个新的 JWT,将其替换在localStorage中,然后循环遍历请求队列并重放所有失败的请求。
如果您设置了/auth/sessions端点,它返回所有当前用户的 RefreshToken,那么您可以显示所有已连接设备的表格。
然后,您可以允许用户删除一行(即从表中 DELETE 某个 RefreshToken)。当受损令牌过期(例如 5 分钟后)并且尝试更新时,它将失败。这就是为什么我们希望 JWT 的生命周期非常短的原因。
这是 JWT 的设计目的。它足够安全,可以被信任。在大型设置(例如 Google)中,身份验证由一个单独的身份验证服务器处理。它负责接受登录/密码以换取令牌。
后来,例如在 Gmail 中,根本不会执行身份验证。Google 读取您的 JWT 并允许您访问您的电子邮件,前提是您的 JWT 没有失效。如果它失效了,您将被重定向到身份验证服务器。
这就是为什么 Google 身份验证在一段时间前发生故障时,一些用户能够毫无问题地使用 Gmail,而其他用户则根本无法连接 - JWT 仍然有效,而其他用户则拥有过期的 JWT。
https的站点才能使 HttpOnly cookie 跨站点工作CREATE TABLE `user_refresh_tokens` (
`user_refresh_tokenID` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`urf_userID` INT(10) UNSIGNED NOT NULL,
`urf_token` VARCHAR(1000) NOT NULL,
`urf_ip` VARCHAR(50) NOT NULL,
`urf_user_agent` VARCHAR(1000) NOT NULL,
`urf_created` DATETIME NOT NULL COMMENT 'UTC',
PRIMARY KEY (`user_refresh_tokenID`)
)
COMMENT='For JWT authentication process';
composer require sizeg/yii2-jwtAuthController.php的控制器。您可以随意命名它。为user_refresh_tokens表创建一个 ActiveRecord 模型。我们将使用类名app\models\UserRefreshToken。
在所有控制器中禁用CSRF验证
添加此属性:public $enableCsrfValidation = false;
/config/params.php中添加 JWT 参数'jwt' => [
'issuer' => 'https://api.example.com', //name of your project (for information only)
'audience' => 'https://frontend.example.com', //description of the audience, eg. the website using the authentication (for info only)
'id' => 'UNIQUE-JWT-IDENTIFIER', //a unique identifier for the JWT, typically a random string
'expire' => 300, //the short-lived JWT token is here set to expire after 5 min.
],
/components中添加JwtValidationData类,它使用我们刚刚设置的参数<?php
namespace app\components;
use Yii;
class JwtValidationData extends \sizeg\jwt\JwtValidationData {
/**
* @inheritdoc
*/
public function init() {
$jwtParams = Yii::$app->params['jwt'];
$this->validationData->setIssuer($jwtParams['issuer']);
$this->validationData->setAudience($jwtParams['audience']);
$this->validationData->setId($jwtParams['id']);
parent::init();
}
}
/config/web.php的配置中添加组件,用于初始化 JWT 身份验证 $config = [
'components' => [
...
'jwt' => [
'class' => \sizeg\jwt\Jwt::class,
'key' => 'SECRET-KEY', //typically a long random string
'jwtValidationData' => \app\components\JwtValidationData::class,
],
...
],
];
AuthController.php,我们必须排除不需要经过身份验证的操作,例如login、refresh-token、options(当浏览器发送跨站点 OPTIONS 请求时)。 public function behaviors() {
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => \sizeg\jwt\JwtHttpBearerAuth::class,
'except' => [
'login',
'refresh-token',
'options',
],
];
return $behaviors;
}
generateJwt()和generateRefreshToken()方法添加到AuthController.php中。我们将在登录/刷新令牌操作中使用它们。如果用户模型不同,请调整类名。 private function generateJwt(\app\models\User $user) {
$jwt = Yii::$app->jwt;
$signer = $jwt->getSigner('HS256');
$key = $jwt->getKey();
$time = time();
$jwtParams = Yii::$app->params['jwt'];
return $jwt->getBuilder()
->issuedBy($jwtParams['issuer'])
->permittedFor($jwtParams['audience'])
->identifiedBy($jwtParams['id'], true)
->issuedAt($time)
->expiresAt($time + $jwtParams['expire'])
->withClaim('uid', $user->userID)
->getToken($signer, $key);
}
/**
* @throws yii\base\Exception
*/
private function generateRefreshToken(\app\models\User $user, \app\models\User $impersonator = null): \app\models\UserRefreshToken {
$refreshToken = Yii::$app->security->generateRandomString(200);
// TODO: Don't always regenerate - you could reuse existing one if user already has one with same IP and user agent
$userRefreshToken = new \app\models\UserRefreshToken([
'urf_userID' => $user->id,
'urf_token' => $refreshToken,
'urf_ip' => Yii::$app->request->userIP,
'urf_user_agent' => Yii::$app->request->userAgent,
'urf_created' => gmdate('Y-m-d H:i:s'),
]);
if (!$userRefreshToken->save()) {
throw new \yii\web\ServerErrorHttpException('Failed to save the refresh token: '. $userRefreshToken->getErrorSummary(true));
}
// Send the refresh-token to the user in a HttpOnly cookie that Javascript can never read and that's limited by path
Yii::$app->response->cookies->add(new \yii\web\Cookie([
'name' => 'refresh-token',
'value' => $refreshToken,
'httpOnly' => true,
'sameSite' => 'none',
'secure' => true,
'path' => '/v1/auth/refresh-token', //endpoint URI for renewing the JWT token using this refresh-token, or deleting refresh-token
]));
return $userRefreshToken;
}
AuthController.php中 public function actionLogin() {
$model = new \app\models\LoginForm();
if ($model->load(Yii::$app->request->getBodyParams()) && $model->login()) {
$user = Yii::$app->user->identity;
$token = $this->generateJwt($user);
$this->generateRefreshToken($user);
return [
'user' => $user,
'token' => (string) $token,
];
} else {
return $model->getFirstErrors();
}
}
AuthController.php中。当 JWT 过期时,调用POST /auth/refresh-token,当用户请求注销时,调用DELETE /auth/refresh-token(然后从客户端的localStorage中删除 JWT 令牌)。 public function actionRefreshToken() {
$refreshToken = Yii::$app->request->cookies->getValue('refresh-token', false);
if (!$refreshToken) {
return new \yii\web\UnauthorizedHttpException('No refresh token found.');
}
$userRefreshToken = \app\models\UserRefreshToken::findOne(['urf_token' => $refreshToken]);
if (Yii::$app->request->getMethod() == 'POST') {
// Getting new JWT after it has expired
if (!$userRefreshToken) {
return new \yii\web\UnauthorizedHttpException('The refresh token no longer exists.');
}
$user = \app\models\User::find() //adapt this to your needs
->where(['userID' => $userRefreshToken->urf_userID])
->andWhere(['not', ['usr_status' => 'inactive']])
->one();
if (!$user) {
$userRefreshToken->delete();
return new \yii\web\UnauthorizedHttpException('The user is inactive.');
}
$token = $this->generateJwt($user);
return [
'status' => 'ok',
'token' => (string) $token,
];
} elseif (Yii::$app->request->getMethod() == 'DELETE') {
// Logging out
if ($userRefreshToken && !$userRefreshToken->delete()) {
return new \yii\web\ServerErrorHttpException('Failed to delete the refresh token.');
}
return ['status' => 'ok'];
} else {
return new \yii\web\UnauthorizedHttpException('The user is inactive.');
}
}
findIdentityByAccessToken(),以通过 JWT 中的 uid 声明查找经过身份验证的用户 public static function findIdentityByAccessToken($token, $type = null) {
return static::find()
->where(['userID' => (string) $token->getClaim('uid') ])
->andWhere(['<>', 'usr_status', 'inactive']) //adapt this to your needs
->one();
}
afterSave()中,清除用户的全部 RefreshToken。 public function afterSave($isInsert, $changedOldAttributes) {
// Purge the user tokens when the password is changed
if (array_key_exists('usr_password', $changedOldAttributes)) {
\app\models\UserRefreshToken::deleteAll(['urf_userID' => $this->userID]);
}
return parent::afterSave($isInsert, $changedOldAttributes);
}
user_refresh_tokens中的记录,并允许他删除他选择记录。Axios 拦截器(使用 React Redux???)
let isRefreshing = false;
let refreshSubscribers: QueuedApiCall[] = [];
const subscribeTokenRefresh = (cb: QueuedApiCall) =>
refreshSubscribers.push(cb);
const onRefreshed = (token: string) => {
console.log("refreshing ", refreshSubscribers.length, " subscribers");
refreshSubscribers.map(cb => cb(token));
refreshSubscribers = [];
};
api.interceptors.response.use(undefined,
error => {
const status = error.response ? error.response.status : false;
const originalRequest = error.config;
if (error.config.url === '/auth/refresh-token') {
console.log('REDIRECT TO LOGIN');
store.dispatch("logout").then(() => {
isRefreshing = false;
});
}
if (status === API_STATUS_UNAUTHORIZED) {
if (!isRefreshing) {
isRefreshing = true;
console.log('dispatching refresh');
store.dispatch("refreshToken").then(newToken => {
isRefreshing = false;
onRefreshed(newToken);
}).catch(() => {
isRefreshing = false;
});
}
return new Promise(resolve => {
subscribeTokenRefresh(token => {
// replace the expired token and retry
originalRequest.headers["Authorization"] = "Bearer " + token;
resolve(axios(originalRequest));
});
});
}
return Promise.reject(error);
}
);
感谢Mehdi Achour为本教程的大部分资料提供帮助。
]]>文章被分成更多文件,因为每个文件在 wiki 上都有最大长度限制。
我已经写过关于翻译是如何工作的。在这里,我将展示如何切换语言并将其保存到 URL 中。因此,让我们将语言切换器添加到主菜单中。
echo Nav::widget([
'options' => ['class' => 'navbar-nav navbar-right'],
'items' => [
['label' => 'Language', 'items' => [
['label' => 'German' , 'url' => \yii\helpers\Url::current(['sys_lang' => 'de']) ],
['label' => 'English', 'url' => \yii\helpers\Url::current(['sys_lang' => 'en']) ],
],
]
现在,我们需要处理新的 GET 参数“sys_lang”并将其保存到 Session 中以保持新语言。最好的方法是创建一个 BaseController,所有控制器都将扩展它。它的内容如下所示
<?php
namespace app\controllers;
use yii\web\Controller;
class _BaseController extends Controller {
public function beforeAction($action) {
if (isset($_GET['sys_lang'])) {
switch ($_GET['sys_lang']) {
case 'de':
$_SESSION['sys_lang'] = 'de-DE';
break;
case 'en':
$_SESSION['sys_lang'] = 'en-US';
break;
}
}
if (!isset($_SESSION['sys_lang'])) {
$_SESSION['sys_lang'] = \Yii::$app->sourceLanguage;
}
\Yii::$app->language = $_SESSION['sys_lang'];
return true;
}
}
如果您想在 URL 中包含 sys_lang,紧跟在域名之后,可以在 config/web.php 中创建以下 URL 规则
'components' => [
// ...
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'rules' => [
// https://yiiframework.cn/doc/api/2.0/yii-web-urlmanager#$rules-detail
// https://stackoverflow.com/questions/2574181/yii-urlmanager-language-in-url
// https://yiiframework.cn/wiki/294/seo-conform-multilingual-urls-language-selector-widget-i18n
'<sys_lang:[a-z]{2}>' => 'site',
'<sys_lang:[a-z]{2}>/<controller:\w+>' => '<controller>',
'<sys_lang:[a-z]{2}>/<controller:\w+>/<action:\w+>' => '<controller>/<action>',
],
],
],
现在,语言切换链接将生成类似这样的 URL:http://myweb.com/en/site/index。如果没有规则,链接将是这样的:http://myweb.com/site/index?sys_lang=en。因此,此规则在两个方向上都有效。当 URL 被解析并且控制器被调用时,以及当使用 URL 助手创建新的 URL 时也是如此。
我使用 Notepad++ 来使用 Regex 进行大规模更改。如果您按下 Ctrl+Shift+F,您将能够在所有文件中进行替换。
Yii::t()
Yii::t('text' , 'text' ) // NO
Yii::t('text','text') // YES
search: Yii::t\('([^']*)'[^']*'([^']*)'[^\)]*\)
replace with: Yii::t\('$1','$2'\)
URL(在 Notepad++ 中)
return $this->redirect('/controller/action')->send(); // NO
return $this->redirect(['controller/action'])->send(); // YES
search: ->redirect\(['][/]([^']*)[']\)
replace: ->redirect\(['$1']\)
====
return $this->redirect('controller/action')->send(); // NO
return $this->redirect(['controller/action'])->send(); // YES
search: ->redirect\((['][^']*['])\)
replace: ->redirect\([$1]\)
PHP 短标签
search: (<\?)([^p=]) // <?if ...
replace: $1php $2 // <?php if ...
// note that sometimes <?xml can be found and it is valid, keep it
视图用法
search: render(Ajax|Partial)?\s*\(\s*['"]\s*[a-z0-9_\/]*(viewName)
Vagrant 和 Docker 都使用您指定的几乎任何 OS 或 SW 配置来创建虚拟机,而源代码位于您的本地磁盘上,因此您可以轻松地在您的 IDE 中,在您的操作系统下修改它们。
不仅可以用于 PHP 开发,还可以用于任何其他情况。
这有什么用?...您的生产服务器运行特定环境,您希望在同一系统上进行开发/测试。此外,您不必在本地安装 XAMPP、LAMP 或其他服务器。您只需启动虚拟机即可。此外,您可以与其他同事共享虚拟系统的配置,以便你们都使用相同环境。您还可以本地运行许多不同的 OS 系统,使用不同的 PHP 版本等。
Vagrant 和 Docker 的工作原理与 composer 或 NPM 相似。它是一个可用的 OS 镜像和其他 SW 的库,您只需选择一些组合即可。整个配置定义在一个文本文件中,名为 Vagrantfile 或 docker-compose.yml,您只需要几个命令即可运行它。调试也不成问题。
信息:本章使用 ScotchBox 中的 PHP 7.0。如果您需要 PHP 7.4,请阅读下一章,其中使用 CognacBox(将在测试后添加)。
基本概述和 Vagrant 配置
所有可用的 Vagrant OS 镜像列表在这里
这两个 Yii 演示应用程序已经包含 Vagrantfile,但它的设置对我来说不清楚 - 它太 PRO 了。所以我想发布我的简化版本,它使用名为scotch/box的 OS 镜像,您也可以将其用于非 Yii PHP 项目。(它有一些优点,缺点是免费版本中使用的 PHP 版本较旧)
Vagrantfile 存储在您的演示项目的根文件夹中。我的 Vagrantfile 只包含以下命令。
Vagrant.configure("2") do |config|
config.vm.box = "scotch/box"
config.vm.network "private_network", ip: "11.22.33.44"
config.vm.hostname = "scotchbox"
config.vm.synced_folder ".", "/var/www/public", :mount_options => ["dmode=777", "fmode=777"]
config.vm.provision "shell", path: "./vagrant/vagrant.sh", privileged: false
end
# Virtual machine will be available on IP A.B.C.D (in our case 11.22.33.44, see above)
# Virtual can access your host machine on IP A.B.C.1 (this rule is given by Vagrant)
它需要文件 vagrant/vagrant.sh,因为我想稍微增强一下服务器。它包含以下内容
# Composer:
# (In case of composer errors, it can help to delete the vendor-folder and composer.lock file)
cd /var/www/public/
composer install
# You can automatically import your SQL (root/root, dbname scotchbox)
#mysql -u root -proot scotchbox < /var/www/public/vagrant/db.sql
# You can run migrations:
#php /var/www/public/protected/yiic.php migrate --interactive=0
# You can create folder and set 777 rights:
#mkdir /var/www/public/assets
#sudo chmod -R 777 /var/www/public/assets
# You can copy a file:
#cp /var/www/public/from.php /var/www/public/to.php
# Installing Xdebug v2 (Xdebug v3 has renamed config params!):
sudo apt-get update
sudo apt-get install php-xdebug
# Configuring Xdebug in php.ini:
# If things do not work, disable your firewall and restart IDE. It might help.
echo "" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "[XDebug]" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.remote_enable=1" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.remote_port=9000" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.remote_autostart=1" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.remote_log=/var/www/public/xdebug.log" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.remote_connect_back=1" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.idekey=netbeans-xdebug" | sudo tee -a /etc/php/7.0/apache2/php.ini
# Important: Make sure that your IDE has identical settings: idekey and remote_port.
# NetBeans: Make sure your project is correctly setup. Right-click the project and select Properties / Run Cofigurations. "Project URL" and "Index file" must have correct values.
# Note:
# Use this if remote_connect_back does not work.
# IP must correspond to the Vagrantfile, only the last number must be 1
#echo "xdebug.remote_handler=dbgp" | sudo tee -a /etc/php/7.0/apache2/php.ini
#echo "xdebug.remote_host=11.22.33.1" | sudo tee -a /etc/php/7.0/apache2/php.ini
sudo service apache2 restart
... 所以在您的项目中创建这两个文件 ...
如果您想手动打开 php.ini 并粘贴此文本,您可以从此处复制它
// sudo nano /etc/php/7.0/apache2/php.ini
// (Xdebug v3 has renamed config params!)
[XDebug]
xdebug.remote_enable=1
xdebug.remote_port=9000
xdebug.remote_autostart=1
xdebug.remote_log=/var/www/public/xdebug.log
xdebug.remote_connect_back=1
xdebug.idekey=netbeans-xdebug
// Important: Make sure that your IDE has identical settings: idekey and remote_port.
// NetBeans: Make sure your project is correctly setup. Right-click the project and select Properties / Run Cofigurations. "Project URL" and "Index file" must have correct values.
要在 PhpStorm 中进行调试,请查看此视频。
要通过 PhpStorm 连接到 MySQL,请查看MilanG 的此评论
安装和使用 Vagrant
首先,请安装 Vagrant 和 VirtualBox。
注意:不幸的是,如今 VirtualBox 在带有 M1 芯片的 ARM 架构 Mac 上无法使用。在这种情况下,请使用 Docker。
重要提示:如果命令“vagrant ssh”需要密码,请输入“vagrant”。
现在,只需打开您的命令行,导航到您的项目,您就可以启动
虚拟机运行后,您还可以调用这些命令
在 Linux shell 中,您可以调用任何您想要的命令。
在 "scotch/box" 中,我不使用 PhpMyAdmin,而是使用 Adminer。它是一个简单的 PHP 脚本,无需任何安装即可运行。只需将 adminer.php 脚本复制到您的 docroot 并通过浏览器访问它。使用与 Yii 配置中相同的登录信息。服务器将是 localhost。
注意:我正在展示高级应用程序。我认为基本应用程序不会有太大区别。很棒的 Docker 教程在这里 here
Yii 项目已经为 Docker 做好了准备。要开始,您只需从 www.docker.com 安装 Docker,然后就可以继续使用本手册。
注意:**init** 和 **composer** 可以本地调用,不一定要通过 Docker 调用。它们只是将文件添加到您的文件夹中。
现在您将能够打开 URL
打开 common/config/main-local.php 并设置以下 DB 连接
使用以下命令之一运行迁移
现在转到前端,点击右上角的 "注册"
第二种方法是直接修改数据库中的表
现在您拥有了您的帐户,您可以登录到后端
只需在 docker-compose.yml 中添加部分 **environment** 即可,如下所示
services:
frontend:
build: frontend
ports:
- 20080:80
volumes:
# Re-use local composer cache via host-volume
- ~/.composer-docker/cache:/root/.composer/cache:delegated
# Mount source-code for development
- ./:/app
environment:
PHP_ENABLE_XDEBUG: 1
XDEBUG_CONFIG: "client_port=9000 start_with_request=yes idekey=netbeans-xdebug log_level=1 log=/app/xdebug.log discover_client_host=1"
XDEBUG_MODE: "develop,debug"
这将允许您看到格式良好的 var_dump 值并在您的 IDE 中调试您的应用程序。
注意:您可以/必须根据您的 IDE 设置指定 **idekey** 和 **client_port**。此外,您的 Yii 项目也必须在 IDE 中配置良好。在 NetBeans 中,请确保 "项目 URL" 和 "索引文件" 在 "属性/运行配置" 中正确(右键单击项目)
注意 2:请记住,xDebug2 和 xDebug3 有不同的设置。详情here.
我在这上面花了大约 8 个小时。希望有人会喜欢它 :-) 可悲的是,此配置不在 docker-compose.yml 中。这将非常方便。
在部分 "volumes" 中添加此行
- ./myphp.ini:/usr/local/etc/php/conf.d/custom.ini
并创建文件 myphp.ini,该文件位于您的 Yii 应用程序的根目录中。例如,您可以输入 **html_errors=on** 和 **html_errors=off** 来测试文件是否已加载。重启 docker 并使用 **phpinfo()** 方法检查 PHP 文件中的结果。
在命令行中导航到您的 docker-project 文件夹并运行命令
列表的最后一列是 NAMES。选择一个并复制其名称。然后运行命令
要找出使用了哪个 Linux,您可以调用 **cat /etc/os-release**。(或检查 Vagrant 章节以了解其他命令)
如果您想找到 php.ini,请键入 **php --ini**。找到它后,您可以将其复制到您的 yii-folder 中,如下所示
cp path/to/php.ini /app/myphp.ini
AdminLTE 是可用的管理主题之一。它目前有两个版本
* 从 Bootstrap3 升级到 Bootstrap4:https://www.youtube.com/watch?v=W1xxvngjep8
AdminLTE <= 2.3、v2.4、v3.0 的文档。请注意,某些 AdminLTE 功能只是第三方依赖项。例如地图。
还有许多其他管理主题
还有更多 Yii2 扩展,用于将 AdminLTE 集成到 Yii 项目中
我选择了 AdminLTE v2(因为它使用与 Yii2 演示相同的 Bootstrap),并且我测试了一些应该有助于实现的扩展。
但让我们从关于如何在 Yii2 演示应用程序中没有扩展的情况下使用 AdminLTE v2 的快速信息开始。
手动集成 v2.4 - 资产文件创建
还要删除所有 SCRIPT 和 LINK 标签。我们将在稍后使用 AssetBundle 添加它们。
我们只需要创建资产文件来链接所有 SCRIPTs 和 LINKs
namespace app\assets;
use yii\web\AssetBundle;
class LteAsset extends AssetBundle
{
public $sourcePath = '@vendor/almasaeed2010/adminlte/';
public $jsOptions = ['position' => \yii\web\View::POS_HEAD]; // POS_END cause conflict with YiiAsset
public $css = [
'bower_components/font-awesome/css/font-awesome.min.css',
'https://fonts.googleapis.ac.cn/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic',
// etc
];
public $js = [
'bower_components/jquery-ui/jquery-ui.min.js',
// etc
];
public $depends = [
'yii\web\YiiAsset',
'yii\bootstrap\BootstrapAsset',
];
}
可能会出现此错误:"Headers already sent"
现在您已经完成了,您可以开始使用 AdminLTE 中的 HTML 和 JS 内容。因此,让我们检查一下将为我们完成此操作的扩展
Insolita 扩展
适用于许多 UI 项目:Boxes、Tile、Callout、Alerts 和 Chatbox。您只需要准备主布局文件和 Asset bundle,如上所述。它自 2018 年以来就没有更新过。
查看其 web 以了解我的评论。我展示了如何使用许多小部件。
源代码中的缺陷
vendor\insolita\yii2-adminlte-widgets\LteConst.php
vendor\insolita\yii2-adminlte-widgets\CollapseBox.php
LteBox
<div class="overlay"><i class="fa fa-refresh fa-spin"></i></div>
Yiister
它的网站解释了一切。非常有用:http://adminlte.yiister.ru 您只需要本文中的 Asset File,然后安装 Yiister 即可。可悲的是,它自 2015 年以来就没有更新过。提供用于渲染菜单、GridView、几个框、Fleshalerts 和 Callouts 的小部件。另外还有错误页面。
dmstr/yii2-adminlte-asset
在 AdminLTE 网站上正式提及。仅渲染菜单和警报。主要提供 Asset 文件和 Gii 模板。Gii 模板会自动修复 GridView 设计,但您可以在下面找到如何手动执行此操作。
其他增强功能
AdminLTE 使用字体 Source Sans Pro。如果您想要使用不同的字体,请在 Google Fonts 上选择它并修改布局文件,如下所示
<link href="https://fonts.googleapis.ac.cn/css2?family=Palanquin+Dark:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Palanquin Dark', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
h1,h2,h3,h4,h5,h6,
.h1,.h2,.h3,.h4,.h5,.h6 {
font-family: 'Palanquin Dark', sans-serif;
}
</style>
要将 GridView 显示为应有的样子,请将其包装在以下 HTML 代码中
<div class="box box-primary">
<div class="box-header">
<h3 class="box-title"><i class="fa fa-table"></i> Grid caption</h3>
</div>
<div class="box-body"
... grid view ...
</div>
</div>
您也可以更改 web/css/site.css 中的 glyphicon
a.asc:after {
content: "\e155";
}
a.desc:after {
content: "\e156";
}
基本上就是这样。现在我们知道如何使用 AdminLTE 并修复 GridView。至少需要一个扩展来渲染小部件,如上所述。
查看关于 Widgets 的官方阅读内容,或者这个 explanation。我正在展示 this example,但我添加了 3 行。这两种类型的小部件都可以这样编码
namespace app\components;
use yii\base\Widget;
use yii\helpers\Html;
class HelloWidget extends Widget{
public $message;
public function init(){
parent::init();
if($this->message===null){
$this->message= 'Welcome User';
}else{
$this->message= 'Welcome '.$this->message;
}
// ob_start();
// ob_implicit_flush(false);
}
public function run(){
// $content = ob_get_clean();
return Html::encode($this->message); // . $content;
}
}
// This widget is called like this:
echo HelloWidget::widget(['message' => ' Yii2.0']);
// After uncommenting my 4 comments you can use this
HelloWidget::begin(['message' => ' Yii2.0']);
echo 'My content';
HelloWidget::end();
运行测试很简单,因为两个演示应用程序都已准备就绪。使用命令行并导航到您的项目。然后键入
php ./vendor/bin/codecept run
这将运行单元测试和功能测试。它们定义在文件夹 tests/unit 和 tests/functional 中。功能测试在隐藏的浏览器中运行,我认为它们不适用于 JavaScript。为了测试复杂的 JavaScript,您需要验收测试。如何在文件 README.md 中找到它们或在 文档 中找到它们,这在两个演示应用程序中都有。如果您想在标准的 Chrome 或 Firefox 浏览器中运行这些测试,则需要 Java JDK 和文件 selenium-server*.jar。请参阅 README.md 中的链接。获得 JAR 文件后,将其放置到您的项目中并运行它
java -jar selenium-server-4.0.0.jar standalone
现在您可以重新运行您的测试。确保您在文件 acceptance.suite.yml 中的 WebDriver 部分具有项目的有效 URL。例如 https:///yii-basic/web。它取决于您的环境。还可以指定浏览器。对我来说,设置“browser: chrome”效果很好。如果您收到错误“WebDriver is not installed”,则需要调用此 composer 命令
composer require codeception/module-webdriver --dev
PS:还有一个文件 ChromeDriver,但我不确定它是否是“codeception/module-webdriver”的替代品或何时使用它。我还没有研究过。
如果您想查看代码覆盖率,请按照文档(上面的链接)中的说明进行操作。另外,请确保您的 PHP 包含 xDebug!并注意 xDebug2 和 xDebug3 的设置差异!如果缺少 xDebug,您将收到错误“No code coverage driver available”。
在 Linux 下我还没有成功,但是当我将 Web 服务器安装在 Windows 上(例如 XAMPP Server)时,我能够安装“Microsoft Access Database Engine 2016 Redistributable”并使用 *.mdb 文件。
所以首先您应该安装带有 PHP 的 Web 服务器,并且您应该知道您是在安装 64 位还是 32 位版本。可能是 64 位。然后转到页面 Microsoft Access Database Engine 2016 Redistributable(或查找更新的版本,如果可用)并安装相应的包(32 位与 64 位)。
注意:如果您已经在相同位版本中安装了 MS Access,则可能不需要安装引擎。
然后您将能够在 DB 连接中使用以下 DSN 字符串。(代码属于文件 config/db.php)
<?php
$file = "C:\\xampp\\htdocs\\Database1.mdb";
return [
'class' => 'yii\db\Connection',
'dsn' => "odbc:DRIVER={Microsoft Access Driver (*.mdb, *.accdb)};Dbq=$file;Uid=;Pwd=;",
'username' => '',
'password' => '',
'charset' => 'utf8',
//'schemaMap' => [
// 'odbc'=> [
// 'class'=>'yii\db\pgsql\Schema',
// 'defaultSchema' => 'public' //specify your schema here
// ]
//],
// Schema cache options (for production environment)
//'enableSchemaCache' => true,
//'schemaCacheDuration' => 60,
//'schemaCache' => 'cache',
];
然后使用它来查询表
$data = Yii::$app->db->createCommand("SELECT * FROM TableX")->queryAll();
var_dump($data);
注意:如果您已经在与您的 PHP 不同的位版本中安装了 MS Access,则您将无法在正确的位版本中安装引擎。在这种情况下,您必须卸载 MS Access。
注意 2:如果您不知道 MDB 文件包含什么内容,Google Docs 向我推荐了 MDB、ACCDB 查看器和阅读器,它有效。
注意 3:Windows 10 中预装了名为
打开您需要的那个,转到“系统 DSN”选项卡并单击“添加”。您将看到哪些驱动程序可用 - **只有这些驱动程序可以在 DSN 字符串中使用!**
如果只有“SQL Server”存在,那么您需要安装带有平台驱动程序的 Access Engine(或 MS Access)。您需要名为 cca "Microsoft Access Driver (*.mdb, *.accdb)" 的驱动程序
在我的情况下,引擎添加了以下 64 位驱动程序
那么 Linux 呢?
您也需要 MS Access 驱动程序,但 Microsoft 不提供它们。有一些第三方 MdbTools 或 EasySoft,但它们要么不完美,要么很贵。另外还有 Unix ODBC。
对于 Java,有 Java JDBC、Jackcess 和 Ucanaccess。
那么 Docker 呢?据我所知,您不能在 Linux 下运行 Windows 镜像,因此在这种情况下您将无法使用 Windows 的 ODBC 优势。您可以在 Windows 下使用 Linux 镜像,但我认为没有办法从虚拟 Linux 访问 ODBC 驱动程序。您必须尝试一下,我还没有测试过。
如果您想将 CSV 导入 Yii2 迁移中的 DB,您可以创建此“迁移基类”并将其用作实际迁移的父类。然后您可以使用方法 batchInsertCsv()。
<?php
namespace app\components;
use yii\db\Migration;
class BaseMigration extends Migration
{
/**
* @param $filename Example: DIR_ROOT . DIRECTORY_SEPARATOR . "file.csv"
* @param $table The target table name
* @param $csvToSqlColMapping [csvColName => sqlColName] (if $containsHeaderRow = true) or [csvColIndex => sqlColName] (if $containsHeaderRow = false)
* @param bool $containsHeaderRow If the header with CSV col names is present
* @param int $batchSize How many rows will be inserted in each batch
* @throws Exception
*/
public function batchInsertCsv($filename, $table, $csvToSqlColMapping, $containsHeaderRow = false, $batchSize = 10000, $separator = ';')
{
if (!file_exists($filename)) {
throw new \Exception("File " . $filename . " not found");
}
// If you see number 1 in first inserted row and column, most likely BOM causes this.
// Some Textfiles begin with 239 187 191 (EF BB BF in hex)
// bite order mark https://en.wikipedia.org/wiki/Byte_order_mark
// Let's trim it on the first row.
$bom = pack('H*', 'EFBBBF');
$handle = fopen($filename, "r");
$lineNumber = 1;
$header = [];
$rows = [];
$sqlColNames = array_values($csvToSqlColMapping);
$batch = 0;
if ($containsHeaderRow) {
if (($raw_string = fgets($handle)) !== false) {
$header = str_getcsv(trim($raw_string, $bom), $separator);
}
}
// Iterate over every line of the file
while (($raw_string = fgets($handle)) !== false) {
$dataArray = str_getcsv(trim($raw_string, $bom), $separator);
if ($containsHeaderRow) {
$dataArray = array_combine($header, $dataArray);
}
$tmp = [];
foreach ($csvToSqlColMapping as $csvCol => $sqlCol) {
$tmp[] = trim($dataArray[$csvCol]);
}
$rows[] = $tmp;
$lineNumber++;
$batch++;
if ($batch >= $batchSize) {
$this->batchInsert($table, $sqlColNames, $rows);
$rows = [];
$batch = 0;
}
}
fclose($handle);
$this->batchInsert($table, $sqlColNames, $rows);
}
}
]]>\yii\mail\BaseMailer::useFileTransport 是一款很棒的工具。如果您激活它,通过此邮件发送的所有电子邮件将被保存(默认情况下)在 @runtime/mail 上,而不是被发送,允许开发人员检查结果。
但是,如果我们想在我们的收件箱中实际收到电子邮件会怎样。当所有电子邮件都应该发送到一个帐户时,没有问题:将其设置为参数并在 params-local.php 中修改它(假设使用高级应用程序模板)。
当应用程序应该将电子邮件发送到不同的帐户并使用 replyTo、cc 和 bcc 字段时,就会出现大问题。几乎不可能用以前的方法来解决它,而且不使用大量的 if(YII_DEBUG)。
好吧,接下来有一个解决方案
'useFileTransport' => true,
'fileTransportCallback' => function (\yii\mail\MailerInterface $mailer, \yii\mail\MessageInterface $message) {
$message->attachContent(json_encode([
'to' => $message->getTo(),
'cc' => $message->getCc(),
'bcc' => $message->getBcc(),
'replyTo' => $message->getReplyTo(),
]), ['fileName' => 'metadata.json', 'contentType' => 'application/json'])
->setTo('debug@mydomain.com') // account to receive all the emails
->setCc(null)
->setBcc(null)
->setReplyTo(null);
$mailer->useFileTransport = false;
$mailer->send($message);
$mailer->useFileTransport = true;
return $mailer->generateMessageFileName();
}
它如何工作?fileTransportCallback 是回调以指定应用于在 @runtime/mail 上创建已保存电子邮件的文件名。它“拦截”发送电子邮件的过程,因此我们可以将其用于我们的目的。
useFileTransportuseFileTransport通过这种方式,我们可以将所有电子邮件都接收在指定的帐户上,并将它们存储在 @runtime/mail 上。
在 Yii2 应用程序上查看电子邮件非常简单的助手。
最初发布在:https://glpzzz.github.io/2020/10/02/yii2-redirect-all-emails.html
]]>在出现很多错误并且不知道如何在 yii2 中执行多个图像 API 之后,我终于在今天得到了它
这是我在论坛上提出的问题,它对我有用 https://forum.yiiframework.com/t/multiple-file-uploading-api-in-yii2/130519
在模型中实现此代码以进行 **多个文件上传**
public function rules()
{
return [
[['post_id', 'media'], 'required'],
[['post_id'], 'integer'],
[['media'], 'file', 'maxFiles' => 10],//here is my file field
[['created_at'], 'string', 'max' => 25],
[['post_id'], 'exist', 'skipOnError' => true, 'targetClass' => Post::className(), 'targetAttribute' => ['post_id' => 'id']],
];
}
您也可以在模型中添加扩展或任何 skiponempty 方法。
这是我在其中执行多个文件上传代码的控制器操作。
public function actionMultiple(){
$model = new Media;
$model->post_id = '2';
if (Yii::$app->request->ispost) {
$model->media = UploadedFile::getInstances($model, 'media');
if ($model->media) {
foreach ($model->media as $value) {
$model = new Media;
$model->post_id = '2';
$BasePath = Yii::$app->basePath.'/../images/post_images';
$filename = time().'-'.$value->baseName.'.'.$value->extension;
$model->media = $filename;
if ($model->save()) {
$value->saveAs($BasePath.$filename);
}
}
return array('status' => true, 'message' => 'Image Saved');
}
}
return array('status' => true, 'data' => $model);
}
如果有任何查询或问题,我会回复。
]]>日志记录是应用程序非常重要的功能。它可以让您知道每时每刻发生了什么。默认情况下,Yii2 基本应用程序和高级应用程序仅配置了一个 \yii\log\FileTarget 目标。
要接收带有应用程序消息的电子邮件,请将日志组件设置为电子邮件(或 Telegram 或 Slack)传输,而不是(或除了)文件传输
'components' => [
// ...
'log' => [
'targets' => [
[
'class' => 'yii\log\EmailTarget',
'mailer' => 'mailer',
'levels' => ['error', 'warning'],
'message' => [
'from' => ['log@example.com'],
'to' => ['developer1@example.com', 'developer2@example.com'],
'subject' => 'Log message',
],
],
],
],
// ...
],
该 \yii\log\EmailTarget 组件是记录消息的另一种方式,在这种情况下,通过应用程序的 mailer 组件以 EmailTarget 配置的 mailer 属性中指定的电子邮件发送它们。请注意,您也可以指定消息属性以及应该通过此目标发送的哪些级别消息。
如果您想通过电子邮件以外的其他平台接收消息,则还有其他组件代表日志目标
或者您可以通过子类化 \yii\log\Target 来实现您自己的子类
]]>https://schema.org 是一种标记系统,允许在网页上嵌入结构化数据,以便搜索引擎和其他应用程序使用。让我们看看如何使用 **JSON-LD** 在基于 Yii2 的网站上的页面中添加 Schema.org。
基本上我们需要在我们的页面中嵌入类似的东西
<script type="application/ld+json">
{
"@context": "http://schema.org/",
"@type": "Movie",
"name": "Avatar",
"director":
{
"@type": "Person",
"name": "James Cameron",
"birthDate": "1954-08-16"
},
"genre": "Science fiction",
"trailer": "../movies/avatar-theatrical-trailer.html"
}
</script>
但我们不喜欢在 Yii2 上编写这样的脚本,所以让我们尝试以其他更 PHP 的方式来做。
在布局中,我们可以为我们的网站定义一些通用标记,因此我们在 @app/views/layouts/main.php 文件的开头添加以下代码段
<?= \yii\helpers\Html::script(isset($this->params['schema'])
? $this->params['schema']
: \yii\helpers\Json::encode([
'@context' => 'https://schema.org',
'@type' => 'WebSite',
'name' => Yii::$app->name,
'image' => $this->image,
'url' => Yi::$app->homeUrl,
'descriptions' => $this->description,
'author' => [
'@type' => 'Organization',
'name' => Yii::$app->name,
'url' => 'https://www.hogarencuba.com',
'telephone' => '+5352381595',
]
]), [
'type' => 'application/ld+json',
]) ?>
这里我们使用 Html::script($content, $options) 来包含具有必要 type 选项的脚本,以及 Json::encode($value, $options) 来生成 JSON。我们还使用一个名为 schema 的页面参数,以允许从其他页面覆盖标记。例如,在 @app/views/real-estate/view.php 中,我们使用
$this->params['schema'] = \yii\helpers\Json::encode([
'@context' => 'https://schema.org',
'@type' => 'Product',
'name' => $model->title,
'description' => $model->description,
'image' => array_map(function ($item) {
return $item->url;
}, $model->images),
'category' => $model->type->description_es,
'productID' => $model->code,
'identifier' => $model->code,
'sku' => $model->code,
'url' => \yii\helpers\Url::current(),
'brand' => [
'@type' => 'Organization',
'name' => Yii::$app->name,
'url' => 'https://www.hogarencuba.com',
'telephone' => '+5352381595',
],
'offers' => [
'@type' => 'Offer',
'availability' => 'InStock',
'url' => \yii\helpers\Url::current(),
'priceCurrency' => 'CUC',
'price' => $model->price,
'priceValidUntil' => date('Y-m-d', strtotime(date("Y-m-d", time()) . " + 365 day")),
'itemCondition' => 'https://schema.org/UsedCondition',
'sku' => $model->code,
'identifier' => $model->code,
'image' => $model->images[0],
'category' => $model->type->description_es,
'offeredBy' => [
'@type' => 'Organization',
'name' => Yii::$app->name,
'url' => 'https://www.hogarencuba.com',
'telephone' => '+5352381595',
]
]
]);
这里我们用更复杂的标记重新定义了此页面的模式:一个带有报价的产品。
这样,我们网站上的所有页面都会定义一个 schema.org 标记:在布局中,我们有一个默认值,在其他页面中,我们可以通过设置 $this->params['schema'] 上的值来重新定义。
OpenGraph 和 Twitter Cards 是两个元数据集,允许描述网页并使其更容易被 Facebook 和 Twitter 分别理解。
要添加到简单网页中的元标签很多,所以让我们使用 TaggedView
此组件覆盖了 yii\web\View,为其添加了更多属性,允许在每个视图中设置值。通常我们使用以下方法设置页面标题
$this->title = $model->title;
现在,使用 **TaggedView**,我们可以设置
$this->title = $model->title;
$this->description = $model->abstract;
$this->image = $model->image;
$this->keywords = ['foo', 'bar'];
这将为该页面生成正确的 OpenGraph、Twitter Card 和 HTML 元描述标签。
此外,我们可以在组件配置中为每个标签定义默认值,这些值将对每个页面可用,并且仅在像前面的示例一样重新定义时才会被覆盖。
'components' => [
//...
'view' => [
'class' => 'daxslab\taggedview\View',
'site_name' => '',
'author' => '',
'locale' => '',
'generator' => '',
'updated_time' => '',
],
//...
]
某些属性有默认值,比如site_name默认值为Yii::$app->name。
网站使用结果
<title>¿Deseas comprar o vender una casa en Cuba? | HogarEnCuba, para comprar y vender casas en Cuba</title>
<meta name="author" content="Daxslab (https://www.daxslab.com)">
<meta name="description" content="Hay 580 casas...">
<meta name="generator" content="Yii2 PHP Framework (https://yiiframework.cn)">
<meta name="keywords" content="HogarEnCuba, ...">
<meta name="robots" content="follow">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:description" content="Hay 580 casas...">
<meta name="twitter:image" content="https://www.hogarencuba.com/images/main-identifier_es.png">
<meta name="twitter:site" content="HogarEnCuba">
<meta name="twitter:title" content="¿Deseas comprar o vender una casa en Cuba?">
<meta name="twitter:type" content="website">
<meta name="twitter:url" content="https://www.hogarencuba.com/">
<meta property="og:description" content="Hay 580 casas...">
<meta property="og:image" content="https://www.hogarencuba.com/images/main-identifier_es.png">
<meta property="og:locale" content="es">
<meta property="og:site_name" content="HogarEnCuba">
<meta property="og:title" content="¿Deseas comprar o vender una casa en Cuba?">
<meta property="og:type" content="website">
<meta property="og:updated_time" content="10 sept. 2020 9:43:00">
]]>文章被分成更多文件,因为每个文件在 wiki 上都有最大长度限制。
您需要在 PHP 中使用 MSSQL 驱动程序。您可以通过编程方式列出它们或测试它们的存在,如下所示
var_dump(\PDO::getAvailableDrivers());
if (in_array('sqlsrv', \PDO::getAvailableDrivers())) {
// ... MsSQL driver is available, do something
}
根据您的系统,您必须下载不同的驱动程序。差异是 x64 与 x86 以及 ThreadSafe 与 nonThreadSafe。在 Windows 中,我总是使用 ThreadSafe。 说明.
最新的 PHP 驱动程序 在这里.
旧的 PHP 驱动程序 在这里.
下载并解压缩驱动程序后,选择一个 DLL 文件并将其放入“php/ext”文件夹中。在 Windows 中,它可能位于此处:“C:\xampp\php\ext”
注意:在某些情况下,您可能还需要 这些 OBDC 驱动程序,但我不知道何时需要
现在必须修改文件 php.ini。在 Windows 中,它可能位于此处:“C:\xampp\php\php.ini”。打开它并搜索以“extension”开头的行,并将以下内容粘贴到其中
extension={filename.dll}
// Example:
extension=php_pdo_sqlsrv_74_ts_x64.dll
现在重启 Apache 并访问 phpinfo() 网页。您应该看到“pdo_sqlsrv”部分。如果您使用的是 XAMPP,它可能位于此 URL 上: https:///dashboard/phpinfo.php.
然后,只需在 Yii2 配置中添加对 MSSQL 数据库的连接即可。在我的情况下,数据库是远程的,因此我需要创建第二个数据库连接。阅读下一章了解如何操作。
在 yii-config 中,添加第二个数据库的方式如下所示
'db' => $db, // the original DB
'db2'=>[
'class' => 'yii\db\Connection',
'driverName' => 'sqlsrv',
// I was not able to specify database like this:
// 'dsn' => 'sqlsrv:Server={serverName};Database={dbName}',
'dsn' => 'sqlsrv:Server={serverName}',
'username' => '{username}',
'password' => '{pwd}',
'charset' => 'utf8',
],
就是这样。现在您可以像这样测试您的数据库
$result = Yii::$app->db2->createCommand('SELECT * FROM {tblname}')->queryAll();
var_dump($result);
请注意,在 MSSQL 中,您可以使用更长的表名。例如:CATEGORY.SCHEMA.TBL_NAME
您的第一个测试模型可以像这样(文件 MyMsModel.php)
namespace app\models;
use Yii;
use yii\helpers\ArrayHelper;
class MyMsModel extends \yii\db\ActiveRecord
{
public static function getDb()
{
return \Yii::$app->db2; // or Yii::$app->get('db2');
}
public static function tableName()
{
return 'CATEGORY.SCHEMA.TBL_NAME'; // or SCHEMA.TBL_NAME
}
}
使用
$result = MyMsModel::find()->limit(2)->all();
var_dump($result);
添加第二个数据库后(如上所述),转到 Gii 中的模型生成器。将 DB 连接更改为您在 yii-config 中命名的连接(在上面的示例中为“db2”),并将表名设置为以下格式:SCHEMA.TBL_NAME。如果 MSSQL 服务器具有多个数据库,其中一个将被设置为主数据库。我认为这将被使用。我还没有成功更改数据库。数据库可以在 DSN 字符串中设置,但在我的情况下,它没有效果。
在之前的章节中,我展示了如何在 Yii 1 中使用 PhpExcel。现在我也需要在 Yii 2 中使用它,而且非常简单。
注意:PhpExcel 已被弃用,并被 PhpSpreadsheet 替换。
// 1) Command line:
// This downloads everything to folder "vendor"
composer require phpoffice/phpspreadsheet --prefer-source
// --prefer-source ... also documentation and samples are downloaded
// ... adds cca 40MB and 1400 files
// ... only for devel system
// 2) PHP:
// Now you can directly use the package without any configuration:
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// Uncomment following rows if you want to set col width:
//$sheet->getColumnDimension('A')->setAutoSize(false);
//$sheet->getColumnDimension('A')->setWidth("50");
$sheet->setCellValue('A1', 'Hello World !');
$writer = new Xlsx($spreadsheet);
// You can save the file on the server:
// $writer->save('hello_world.xlsx');
// Or you can send the file directly to the browser so user can download it:
// header('Content-Type: application/vnd.ms-excel'); // This is probably for older XLS files.
header('Content-Type: application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); // This is for XLSX files (they are basically zip files).
header('Content-Disposition: attachment;filename="filename.xlsx"');
header('Cache-Control: max-age=0');
$writer->save('php://output');
exit();
感谢 DbCreator 的关于如何将 XLSX 发送到浏览器的想法。但是,不应调用 exit() 或 die()。请阅读链接。
以下是我的想法,它源于 Yii2 中的 renderPhpFile() 方法
ob_start();
ob_implicit_flush(false);
$writer->save('php://output');
$file = ob_get_clean();
return \Yii::$app->response->sendContentAsFile($file, 'file.xlsx',[
'mimeType' => 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'inline' => false
]);
这对我也适用
$tmpFileName = uniqid('file_').'.xlsx';
$writer->save($tmpFileName);
header('Content-Type: application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="filename.xlsx"');
header('Cache-Control: max-age=0');
echo file_get_contents($tmpFileName);
unlink($tmpFileName);
exit();
注意:但是不应调用 exit() 或 die()。请阅读上面的“DbCreator”链接。
有关其他 PDF 创建器,请参阅本指南的第一部分
TCPDF 创建于 2002 年(我认为),如今(2020 年)正在被重写为现代 PHP 应用程序。我将介绍两者,但让我们从旧版本开始。
旧版本的 TCPDF
从 GitHub 下载并保存到文件夹中
{projectPath}/_tcpdf
在 web/index.php 中添加以下内容
require_once('../_tcpdf/tcpdf.php');
现在您可以使用任何示例来测试 TCPDF。例如:https://tcpdf.org/examples/example_001/
注意:您必须使用反斜杠调用构造函数
$pdf = new \TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
注意:使用多种方法打印文本 - 请参阅 tcpdf.php 文件以获取详细信息
注意:将文件存储为 UTF8 无 BOM 格式,以便 PDF 中的变音符号正确显示。
导入新的 TTF 字体,操作如下
// this command creates filed in folder _tcpdf\fonts. Use the filename as the fontname in other commands.
$fontname = \TCPDF_FONTS::addTTFfont("path to TTF file", 'TrueTypeUnicode', '', 96);
现在您可以在 PHP 中像这样使用它
$pdf->SetFont($fontname, '', 24, '', true);
或者在 HTML 中
<font size="9" face="fontName" style="color: rgb(128, 128, 128);">ABC</font>
渲染方式如下
$pdf->writeHTML($html);
注意:在将 pageNr 和 totalPageCount 打印到页脚时,writeHTML() 无法像 示例 3 中所示的那样正确解释 getAliasNumPage() 和 getAliasNbPages() 方法。我必须使用 Text() 渲染方法并将数字正确放置,如下所示
$this->writeHTML($footerHtmlTable);
$this->SetTextColor('128'); // I have gray pageNr
$this->Text(185, 279, 'Page ' . $this->getAliasNumPage() . '/' . $this->getAliasNbPages());
$this->SetTextColor('0'); // returning black color
新版本的 TCPDF
...待续...
如果我生成一个 PDF 发票,它包含许多数字,将它们打印为整数(如果不需要小数)会很好看。例如,数字 24 看起来比 24.00 更好,并且节省了空间。因此我创建了这样的格式器。原始灵感和操作方法见此
我的格式器如下所示
<?php
namespace app\myHelpers;
class MyFormatter extends \yii\i18n\Formatter {
public function asDecimalOrInteger($value) {
$intStr = (string) (int) $value; // 24.56 => "24" or 24 => "24"
if ($intStr === (string) $value) {
// If input was integer, we are comparing strings "24" and "24"
return $this->asInteger($value);
}
if (( $intStr . '.00' === (string) $value)) {
// If the input was decimal, but decimals were all zeros, it is an integer.
return $this->asInteger($value);
}
// All other situations
$decimal = $this->asDecimal($value);
// Here I trim also the trailing zero.
// Disadvantage is that String is returned, but in PDF it is not important
return rtrim((string)$decimal, "0");
}
}
用法很简单。阅读上面的链接并点赞 karpy47 或者参见下方
// file config/web.php
'components' => [
'formatter' => [
'class' => 'app\myHelpers\MyFormatter',
],
],
整个 Yii 中只有一个格式器,您可以扩展它 = 您可以添加更多方法,格式器的其余部分将保持不变,因此您可以使用文档中提到的所有其他方法。
...可以通过将 MySQL 视图添加到您的数据库中,为其创建模型并在“ParentSearch”模型中将其用作基类来轻松完成。
让我们在发票及其项目的列表中显示它。发票位于“invoice”表中(模型 Invoice),其项目位于“invoice_item”中(模型 InvoiceItem)。现在我们需要将它们连接起来,并按价格(金额)的 SUM 进行排序和过滤。为了避免在 PHP 中进行计算,如果我们准备一个 MySQL 视图,数据库可以为我们执行此操作
CREATE VIEW v_invoice AS
SELECT invoice.*,
SUM(invoice_item.units * invoice_item.price_per_unit) as amount,
COUNT(invoice_item.id) as items
FROM invoice
LEFT JOIN invoice_item
ON (invoice.id = invoice_item.id_invoice)
GROUP BY invoice.id
注意:在这里,您可以阅读为什么最好不要在 LEFT JOIN 中使用 COUNT(*)
这将在技术上将原始表“invoice”克隆到“v_invoice”,并将添加 2 个计算列:“amount” + “items”。现在您可以轻松地将此视图用作表(仅供读取),并将其显示在 GridView 中。如果您已经为“invoice”表创建了 GridView,则更改非常小。创建此模型
<?php
namespace app\models;
class v_Invoice extends Invoice
{
public static function primaryKey()
{
// here is specified which column(s) create the fictive primary key in the mysql-view
return ['id'];
}
public static function tableName()
{
return 'v_invoice';
}
}
.. 并在 InvoiceSearch 模型中用 v_Invoice 替换 Invoice(我认为有 2 个地方),并为这些新列添加规则。示例
public function rules()
{
return [
// ...
[['amount'], 'number'], // decimal
[['items'], 'integer'],
];
}
在 search() 方法中添加条件,以确定是否要按金额或项目进行过滤
if (trim($this->amount)!=='') {
$query->andFilterWhere([
'amount' => $this->amount
]);
}
在 GridView 中,您现在可以使用“amount”和“items”列作为本机列。过滤和排序将起作用。
危险:阅读以下内容,了解如何按关联列进行搜索和排序。如果您想将 MySQL 与另一个表连接起来,这可能无法正常工作。
我认为这种方法是最简单的实现目标的方法。优点是,只有在调用 search() 方法时才会使用 MySQL 视图 - 这意味着它是在发票列表中使用的。Web 的其他部分不受影响,因为它们使用的是原始 Invoice 模型。但是,如果您需要来自 Invoice 模型的某些特殊方法,您也可以在 v_Invoice 中使用它。如果数据被保存或更改,您必须始终修改原始表“invoice”。
假设您有一个发票表和一个公司表。它们之间存在关系,您想显示发票列表以及每一行中相应的公司名称。您想按此列进行过滤和排序。
您的 GridView
<?= GridView::widget([
// ...
'columns' => [
// ...
[
'attribute'=>'company_name',
'value'=>'companyRelation.name',
],
您的 InvoiceSearch 模型
class InvoiceSearch extends Invoice
{
public $company_name;
// ...
public function rules() {
return [
// ...
[['company_name'], 'safe'],
];
}
// ...
public function search($params) {
// ...
// You must use joinWith() in order to have both tables in one JOIN - then you can call WHERE and ORDER BY on the 2nd table.
// Explanation here:
// https://stackoverflow.com/questions/25600048/what-is-the-difference-between-with-and-joinwith-in-yii2-and-when-to-use-them
$query = Invoice::find()->joinWith('companyRelation');
// Appending new sortable column:
$sort = $dataProvider->getSort();
$sort->attributes['company_name'] = [
'asc' => ['table.column' => SORT_ASC],
'desc' => ['table.column' => SORT_DESC],
'label' => 'Some label',
'default' => SORT_ASC
];
// ...
if (trim($this->company_name)!=='') {
$query->andFilterWhere(['like', 'table.column', $this->company_name]);
}
}
在我的 Yii v1 教程中,我介绍了以下方法:手动发送标题,然后调用 exit()。但是,调用 exit() 或 die() 不是一个好主意,因此我在 Yii v2 中发现了一种更好的方法。请参阅章节 安全(秘密)文件下载
动机:有时,您会收到使用 base64 编码为字符串的 PDF 文件。例如,来自 FedEx、DPD 或其他快递公司的带有条形码的标签,您的任务是将标签显示给用户。
对我来说,以下算法起作用
$pdfBase64 = 'JVBERi0xLjQ ... Y0CiUlRU9GCg==';
// First I create a fictive stream in a temporary file
// Read more about PHP wrappers:
// https://php.ac.cn/manual/en/wrappers.php.php
$stream = fopen('php://temp','r+');
// Decoded base64 is written into the stream
fwrite($stream, base64_decode($pdfBase64));
// And the stream is rewound back to the start so others can read it
rewind($stream);
// This row sets "Content-Type" header to none. Below I set it manually do application/pdf.
Yii::$app->response->format = Yii::$app->response::FORMAT_RAW;
Yii::$app->response->headers->set('Content-Type', 'application/pdf');
// This row will download the file. If you do not use the line, the file will be displayed in the browser.
// Details here:
// https://mdn.org.cn/en-US/docs/Web/HTTP/Headers#Downloads
// Yii::$app->response->headers->set('Content-Disposition','attachment; filename="hello.pdf"');
// Here is used the temporary stream
Yii::$app->response->stream = $stream;
// You can call following line, but you don't have to. Method send() is called automatically when current action ends:
// Details here:
// https://yiiframework.cn/doc/api/2.0/yii-web-response#sendContentAsFile()-detail
// return Yii::$app->response->send();
注意:如果需要,您可以添加更多标题。请查看我之前发表的文章(上面链接)。
]]>注意:Pantahub 是唯一一个可以共享和部署任何设备的 Linux 固件的地方。您可以在此处注册 @pantahub:http://www.pantahub.com
运行 $ unxz rpi3_initial_stable.img.xz


注意:pvr 是一款 CLI 工具,可用于通过 pantahub 平台与您的设备进行交互。
注意:使用 pvr,您可以像使用 git 树一样轻松地共享您的固件和项目。
注意:下载后,将 pvr 二进制文件移至您的 bin 文件夹。
Linux(AMD64):下载
Linux(ARM32v6):下载
Darwin(AMD64):下载
pvr clone; pvr commit; pvr post
从 github 源代码安装:$ go get gitlab.com/pantacor/pvr $ go build -o ~/bin/pvr gitlab.com/pantacor/pvr
注意:您需要在系统中安装“GOLANG”才能从 github 源代码构建 pvr。

$ pvr scan ¶ 
$ pvr claim -c merely-regular-gorilla https://api.pantahub.com:443/devices/5f1b9c44e193a Watch now on Amazon Prime Video 5000afa9901


$ pvr clone https://pvr.pantahub.com/sirinibin/presently_learning_pelican/0 presently_learning_pelican

现在您的设备已准备好部署您的 Yii2 应用
`$ cd presently_learning_pelican`
>sirinibin/yii2-basic-arm32v7:latest 是一个为具有 ARM32 架构的设备制作的 Docker Watch now on Amazon Prime Video 镜像 >> 您可以为您的自定义 Yii2 应用定制 Docker 镜像。
$ pvr app add yii2 --from=sirinibin/yii2-basic-arm32v7:latest

$ pvr add . $ pvr commit $ pvr post 
状态 1

状态 2

状态 3

状态 4

访问设备 IP:http://10.42.0.231/myapp1/web/ 在您的网页浏览器中。 
]]>您已完成!
Yii2 - 从 Bootstrap3 转换为 Bootstrap4
撰写本文是因为尽管转换过程在很大程度上是轻松无痛的,但也存在一些小问题。这些问题并不难解决,但问题所在并不一目了然。
1 - 安装 Bootstrap4 我更喜欢直接使用 composer。更改项目根目录中的 composer.json
复制该行,并更改新行
"yiisoft/yii2-bootstrap" : "~2.0.6", "yiisoft/yii2-bootstrap4" : "^2.0.8",
yii\bootstrap\ 并将其更改为 yii\bootstrap4\。但是,请注意 - 您的 IDE 可能要求转义反斜杠。例如,Eclipse 可以轻松地找到包含该字符串的所有文件,但搜索字符串必须使用双反斜杠,而替换字符串必须保留为单个反斜杠。NavBar::begin 开头的行,并查看其类选项。在我的情况下,它们是最初的:'class' => 'navbar-inverse navbar-fixed-top'最后,我将类行更改为如下所示,它为我提供了一个与原始导航栏非常相似的导航栏
//Bootstrap3: 'class' => 'navbar-inverse navbar-fixed-top',
//Changed for Bootstrap4:
'class' => 'navbar navbar-expand-md navbar-light bg-dark',
面包屑
注意 - 2020 年 3 月:有关面包屑的整个部分可能不再是问题。虽然我将该部分保留为教程,但在进行任何更改之前,请阅读 Davide 在用户评论中所说的话。
所以,这修复了我的导航栏。接下来,我注意到面包屑不太正确 - 分隔路径元素的斜杠不见了。为了准备大量调试,我去了 Bootstrap 网站寻找一些灵感。我不必再看了 - Bootstrap 4 要求每个面包屑元素都有一个“breadcrumb-item”类。在我花了一些时间查看 vendors/yiisoft/yii2/widgets/Breadcrumbs.php 以了解问题后,我发现只需要更改 itemTemplate 和 activeItemTemplate 即可。当然,由于这些是 Yii2 框架的一部分,您不想更改该文件,否则,它可能会在某个阶段进行更新,而您所做的所有更改都会丢失。由于这两个属性都是公开的,因此您可以从类外部更改它们,最简单的方法是在 frontend/views/main.php 中:`html
<div class="container">
<?= Breadcrumbs::widget([
'itemTemplate' => "\n\t<li class=\"breadcrumb-item\"><i>{link}</i></li>\n", // template for all links
'activeItemTemplate' => "\t<li class=\"breadcrumb-item active\">{link}</li>\n", // template for the active link
'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
]) ?>
<?= Alert::widget() ?>
<?= $content ?>
</div>```
数据网格操作列 我其中一个页面是 gii 为我生成的。在每一行上,它都有一组按钮,您可以点击它们来查看、编辑或删除该行。在 Bootstrap 4 下,操作列消失了。查看页面源代码显示它在那里,但我看不见它,也无法点击它。转到迁移指南,事实证明 Bootstrap 3 包含图标,而 Bootstrap 4 则不包含。我在 Yii2forum 中提出的问题 中得到了很多帮助。最后,我的解决方案是通过在 composer.json 的 require 部分中包含一行“fortawesome/font-awesome”: "^5.12.1" 来获得 FontAwesome 5 的本地副本,然后选择我想要的图标。我花了很多时间弄清楚如何做到这一点,但是当我完成时,它似乎很简单,几乎是反高潮。这是我在我的数据表单中所做的
['class' => 'yii\grid\ActionColumn',
'buttons' => [
'update' => function($url,$model) {
return Html::a('<i class="fas fa-edit"></i>', $url, [
'title' => Yii::t('app', 'update')
]);
},
'view' => function($url,$model) {
return Html::a('<i class="fas fa-eye"></i>', $url, [
'title' => Yii::t('app', 'view')
]);
},
'delete' => function($url,$model) {
return Html::a('<i class="fas fa-trash"></i>', $url, [
'title' => Yii::t('app', 'delete')
]);
}
]
],
功能测试
没有看到任何更直观的东西,至少没有明显的东西,我现在运行了测试套件。这些测试以前都是通过的,但现在有几个测试失败了。其中一个是联系表格,因此我单独运行了该测试,测试告诉我它们失败了,因为它们看不见错误消息
1) ContactCest: Check contact submit no data
Test ../frontend/tests/functional/ContactCest.php:checkContactSubmitNoData
Step See "Name cannot be blank",".help-block"
Fail Element located either by name, CSS or XPath element with '.help-block' was not found.
另一方面,我可以在表单上看到错误消息,所以我使用了浏览器的页面源代码并发现 css 类不再是“help-block”,它已更改为“invalid-feedback”。很容易 - 在 frontend/tests/_support/FunctionalTester.php 中,我更改了预期的 css 类
public function seeValidationError($message)
{
$this->see($message, '.invalid-feedback');
}
当然,这只是一个片段,只是举例说明。我发现必须在几个地方做同样的事情,但所有这些都易于找到和解决。
在这之后,运行我的测试没有发现其他问题,但我并不认为这意味着 没有 其他问题。虽然到目前为止一切似乎都正常,但我预计还有更多问题隐藏在幕后。不知何故,这些问题似乎不再那么难以克服了。
]]>