源码阅读 - 初始 : (2) 初始化 App

初始化 Application

根据指定的路径来初始化应用, 原始码

1
2
3
$app = new Illuminate\Foundation\Application(
realpath(__DIR__ . '/../')
);

Illuminate/Foundation/Application::__construct()

1
2
3
4
5
6
7
8
9
// 1) 注册基本绑定
$this->registerBaseBindings();

// 2) 注册服务提供者
// 如果存在 register 方法, 则执行并且标记为已经注册过. 如果系统已经启动, 则直接运行 boot 方法
$this->registerBaseServiceProviders();

// 3) 注册核心 Alias
$this->registerCoreContainerAliases();

1) 注册基本绑定

1
2
3
4
5
6
7
8
# 1) 实例化 app
$this->instance('app', $this);

# 实例化 container => app
$this->instance(Container::class, $this);

# 2) 实例化 PackageManifest
$this->instance(PackageManifest::class, new PackageManifest(...));

1-1) 实例化 app

实例化 App[Application::instance()]

这里拿 ('app', $this)为例子来阅读源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 移除抽象关联
$this->removeAbstractAlias($abstract);

// 确定给定的抽象类型是否已经绑定
// 已经设定 Alias
$isBound = $this->bound($abstract);

// 移除关联
unset($this->aliases[$abstract]);

// 检测此类型是否被绑定过, 如果已经绑定会重新触发绑定并且更新类
// 实例化 instances['app'] = $this
$this->instances[$abstract] = $instance;

// 重新绑定
if ($isBound) {
$this->rebound($abstract);
// 1) make -> alias (resolve)
# $this->make($abstract)

// 触发回调
# foreach ($this->getReboundCallbacks($abstract) as $callback) {
# call_user_func($callback, $this, $instance);
# }
}

1-2) 实例化 PackageManifest

2) 注册事件处理

1
2
3
4
5
6
7
8
// 注册事件处理器
$this->register(new EventServiceProvider($this));

// 注册日志服务
$this->register(new LogServiceProvider($this));

// 注册路由服务
$this->register(new RoutingServiceProvider($this));

2-1) Application::register() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* sample service providers
$this->serviceProviders = [
Illuminate\Events\EventServiceProvider Object
Illuminate\Log\LogServiceProvider Object
Illuminate\Routing\RoutingServiceProvider Object
];
*/

// 获取注册的对象, 传递($provider) 可以是对象/字串
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}

// 如果给定的 provider 是字串, 则返回实例化的对象.
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
# new $provider($this)
}

// 检测给定的对象是否存在 register 方法, 存在则执行 register 方法并标注为已注册
if (method_exists($provider, 'register')) {
$provider->register();
}
$this->markAsRegistered($provider);

// todo - 什么时候启动
// 如果应用已经启动, 我们便有机会启动 ServiceProvider 的 boot 逻辑, 便于开发者持续开发
if ($this->booted) {
$this->bootProvider($provider);
# 启动 boot 方法
# if (method_exists($provider, 'boot')) {
# return $this->call([$provider, 'boot']);
# }
}

return $provider;

2-2) EventServiceProvider

1
2
3
4
5
6
// 注册事件触发器, 并且配置队列执行
$this->app->singleton('events', function ($app) {
return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
return $app->make(QueueFactoryContract::class);
});
});

2-3) LogServiceProvider

1
2
3
4
// 注册日志
$this->app->singleton('log', function () {
return $this->createLogger();
});

2-4) RoutingServiceProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 注册路由
$this->registerRouter();

// url 生成
$this->registerUrlGenerator();

// 定向器
$this->registerRedirector();

// psr 请求
$this->registerPsrRequest();

// psr 响应
$this->registerPsrResponse();

// 响应工厂
$this->registerResponseFactory();

// 控制器触发器
$this->registerControllerDispatcher();

3) 注册 Alias

将指定的类和 app 示例做绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$allAlias = [
'app' => [
\Illuminate\Foundation\Application::class,
\Illuminate\Contracts\Container\Container::class,
\Illuminate\Contracts\Foundation\Application::class,
\Psr\Container\ContainerInterface::class
],
...
'view' => [
\Illuminate\View\Factory::class,
\Illuminate\Contracts\View\Factory::class
],
];

foreach($allAlias = as $key => $aliases) {
foreach ($aliases as $alias) {
// key : app
// alias : Illuminate\Foundation\Application::class
$this->alias($key, $alias);
}
}

3-1) 系统 alias 项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
$alias = [
'app' => [
\Illuminate\Foundation\Application::class,
\Illuminate\Contracts\Container\Container::class,
\Illuminate\Contracts\Foundation\Application::class,
\Psr\Container\ContainerInterface::class
],
'auth' => [
\Illuminate\Auth\AuthManager::class,
\Illuminate\Contracts\Auth\Factory::class
],
'auth.driver' => [
\Illuminate\Contracts\Auth\Guard::class
],
'blade.compiler' => [
\Illuminate\View\Compilers\BladeCompiler::class
],
'cache' => [
\Illuminate\Cache\CacheManager::class,
\Illuminate\Contracts\Cache\Factory::class
],
'cache.store' => [
\Illuminate\Cache\Repository::class,
\Illuminate\Contracts\Cache\Repository::class
],
'config' => [
\Illuminate\Config\Repository::class,
\Illuminate\Contracts\Config\Repository::class
],
'cookie' => [
\Illuminate\Cookie\CookieJar::class,
\Illuminate\Contracts\Cookie\Factory::class,
\Illuminate\Contracts\Cookie\QueueingFactory::class
],
'encrypter' => [
\Illuminate\Encryption\Encrypter::class,
\Illuminate\Contracts\Encryption\Encrypter::class
],
'db' => [
\Illuminate\Database\DatabaseManager::class
],
'db.connection' => [
\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class
],
'events' => [
\Illuminate\Events\Dispatcher::class,
\Illuminate\Contracts\Events\Dispatcher::class
],
'files' => [
\Illuminate\Filesystem\Filesystem::class
],
'filesystem' => [
\Illuminate\Filesystem\FilesystemManager::class,
\Illuminate\Contracts\Filesystem\Factory::class
],
'filesystem.disk' => [
\Illuminate\Contracts\Filesystem\Filesystem::class
],
'filesystem.cloud' => [
\Illuminate\Contracts\Filesystem\Cloud::class
],
'hash' => [
\Illuminate\Contracts\Hashing\Hasher::class
],
'translator' => [
\Illuminate\Translation\Translator::class,
\Illuminate\Contracts\Translation\Translator::class
],
'log' => [
\Illuminate\Log\Writer::class,
\Illuminate\Contracts\Logging\Log::class, \Psr\Log\LoggerInterface::class
],
'mailer' => [
\Illuminate\Mail\Mailer::class,
\Illuminate\Contracts\Mail\Mailer::class,
\Illuminate\Contracts\Mail\MailQueue::class
],
'auth.password' => [
\Illuminate\Auth\Passwords\PasswordBrokerManager::class,
\Illuminate\Contracts\Auth\PasswordBrokerFactory::class
],
'auth.password.broker' => [
\Illuminate\Auth\Passwords\PasswordBroker::class,
\Illuminate\Contracts\Auth\PasswordBroker::class
],
'queue' => [
\Illuminate\Queue\QueueManager::class,
\Illuminate\Contracts\Queue\Factory::class,
\Illuminate\Contracts\Queue\Monitor::class
],
'queue.connection' => [
\Illuminate\Contracts\Queue\Queue::class
],
'queue.failer' => [
\Illuminate\Queue\Failed\FailedJobProviderInterface::class
],
'redirect' => [
\Illuminate\Routing\Redirector::class
],
'redis' => [
\Illuminate\Redis\RedisManager::class,
\Illuminate\Contracts\Redis\Factory::class
],
'request' => [
\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class
],
'router' => [
\Illuminate\Routing\Router::class,
\Illuminate\Contracts\Routing\Registrar::class,
\Illuminate\Contracts\Routing\BindingRegistrar::class
],
'session' => [
\Illuminate\Session\SessionManager::class
],
'session.store' => [
\Illuminate\Session\Store::class,
\Illuminate\Contracts\Session\Session::class
],
'url' => [
\Illuminate\Routing\UrlGenerator::class,
\Illuminate\Contracts\Routing\UrlGenerator::class
],
'validator' => [
\Illuminate\Validation\Factory::class,
\Illuminate\Contracts\Validation\Factory::class
],
'view' => [
\Illuminate\View\Factory::class,
\Illuminate\Contracts\View\Factory::class
],
];

3-2) alias 结果

1
2
3
4
5
6
7
8
9
10
/* $this->aliases = [
[Illuminate\Foundation\Application] => app
[Illuminate\Contracts\Container\Container] => app
[Illuminate\Contracts\Foundation\Application] => app
[Psr\Container\ContainerInterface] => app
[Illuminate\Auth\AuthManager] => auth
[Illuminate\Contracts\Auth\Factory] => auth
...
]
*/

singleton Kernel 和异常处理

原始代码

step 01:singleton

1
2
3
4
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);

step 02:bind

1
2
// 抽象类绑定到实体类
$this->bind($abstract, $concrete, true);

step 03:bind code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 如果没有给定实体类型, 我们简单设置实体类型为虚拟类型, 在那之后
// 在此之后,将注册为共享的具体类型,而不需要在两个参数中强制声明它们的类
$this->dropStaleInstances($abstract);

if (is_null($concrete)) {
$concrete = $abstract;
}

// 如果工厂不是闭包,这意味着它只是一个类名,它被绑定到这个容器中
// 我们把抽象类型包装在它自己的闭包中,以便在扩展时给我们更多的便利。
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}

$this->bindings[$abstract] = compact('concrete', 'shared');

// 如果这个类型已经存在, 我们触发重新绑定
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}

安装扩展

当前执行环境为 7.2

安装 redis

1
$ pecl install redis

在 安装 redis 的时候需要 igbinary.h

1
2
3
4
checking for json includes... /usr/local/Cellar/php@7.2/7.2.29/include/php
checking for redis json support... enabled
checking for igbinary includes... configure: error: Cannot find igbinary.h
ERROR: `/private/tmp/pear/temp/redis/configure --with-php-config=/usr/local/opt/php@7.2/bin/php-config --enable-redis-igbinary=y --enable-redis-lzf=y --enable-redis-zstd=y' failed
1
$ pecl install igbinary

可能又会出现

1
2
3
4
...
checking for libzstd files in default path... not found
configure: error: Please reinstall the libzstd distribution
ERROR: `/private/tmp/pear/temp/redis/configure --with-php-config=/usr/local/opt/php@7.2/bin/php-config --enable-redis-igbinary=y --enable-redis-lzf=y --enable-redis-zstd=y' failed

安装 swoole

1
$ pecl install swoole

在安装 swoole 的时候会遇到

错误信息如下:

1
2
3
4
5
6
7
8
....
In file included from /private/tmp/pear/temp/swoole/php_swoole.h:53:
/private/tmp/pear/temp/swoole/include/swoole.h:620:10: fatal error: 'openssl/ssl.h' file not found
#include <openssl/ssl.h>
^~~~~~~~~~~~~~~
1 error generated.
make: *** [php_swoole_cxx.lo] Error 1
ERROR: `make' failed

没有加 openssl 库的路径或者指定 openssl 库的路径不对,缺少头文件。
没有 openssl 的话使用 brew 安装一个 openssl,在 pecl 安装的时候加上对应路径即可

1
enable openssl support? [no] : yes --with-openssl-dir=/usr/local/opt/openssl@1.1

[转] laravel-mysql 读写分离

原文地址 : laravel-mysql 读写分离

使用【如果不想了解源代码,直接看 3 种使用方式就好】

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
'mysql' => [
'write' => [
'host' => '127.0.0.1',
'port' => 3306
],
'read' => [
[
'host' => '127.0.0.1',
'port' => 3307
],
],
'driver' => 'mysql',
// 'host' => env('DB_HOST', '127.0.0.1'),
// 'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
// 'charset' => 'utf8mb4',
// 'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
// 'sticky' => true, // laravel 5.5 新增
],

使用写库读数据的三种方式

  • 方法 1:
1
$user = DB::selectFromWriteConnection('select * from users where id=42111');
  • 方法 2:
1
User::onWriteConnection()->find($id);
  • 方法 3:

通过配置  'sticky' => true,

一 配置过程

config/database.php 里面配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
'mysql' => [
'write' => [
'host' => '192.168.1.180',
],
'read' => [
['host' => '192.168.1.182'],
['host' => '192.168.1.179'],
],
'sticky' => true, // laravel 5.5 新增
'driver' => 'mysql',
'port' => env('DB_PORT', '3306'),
'unix_socket' => env('DB_SOCKET', ''),
'engine' => null,
'database' => 'database',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
]

加强版,支持多主多从,支持独立用户名和密码,配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
'mysql' => [
'write' => [
[
'host' => '192.168.1.180',
'username' => '',
'password' => '',
],
],
'read' => [
[
'host' => '192.168.1.182',
'username' => '',
'password' => '',
],
[
'host' => '192.168.1.179',
'username' => '',
'password' => '',
],
],
'driver' => 'mysql',
'database' => 'database',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
]

设置完毕之后,Laravel5 默认将  select  的语句让  read  指定的数据库执行,insert/update/delete  则交给  write  指定的数据库,达到读写分离的作用。

这些设置对原始查询  raw queries,查询生成器  query builder,以及  Eloquent ORM  都生效。

官网解释如下:

Sometimes you may wish to use one database connection for SELECT statements, and another for INSERT, UPDATE, and DELETE statements. Laravel makes this a breeze, and the proper connections will always be used whether you are using raw queries, the query builder, or the Eloquent ORM

验证

开启 MySQL 的  general-log ,通过  tail -f  的方式监控 log 变化来确定配置是否生效

注意点 1:

sticky  是一个 可选的 选项,它可用于立即读取在当前请求周期内已写入数据库的记录。

如果  sticky  选项被启用,并且在当前的请求周期内在数据库执行过「写入」操作,那么任何「读取」的操作都将使用「写入」连接。这可以确保在请求周期内写入的任何数据可以在同一请求期间立即从数据库读回。这个选项的作用取决于应用程序的需求。【sticky 选项是一个可选的配置值,可用于在当前请求生命周期内允许立即读取写入数据库的记录。如果 sticky 选项被启用并且一个”写”操作在当前生命周期内发生,则后续所有”读”操作都会使用这个”写”连接(前提是同一个请求生命周期内),这样就可以确保同一个请求生命周期内写入的数据都可以立即被读取到,从而避免主从延迟导致的数据不一致,是否启用这一功能取决于你。】

当然,这只是一个针对分布式数据库系统中主从数据同步延迟的一个非常初级的解决方案,访问量不高的中小网站可以这么做,大流量高并发网站肯定不能这么干,主从读写分离本来就是为了解决单点性能问题,这样其实是把问题又引回去了,造成所有读写都集中到写数据库,对于高并发频繁写的场景下,后果可能是不堪设想的,但是话说回来,对于并发量不那么高,写操作不那么频繁的中小型站点来说,sticky 这种方式不失为一个初级的解决方案。

注意点 2:

注:目前读写分离仅支持单个写连接。

二 实现原理

Laravel5 读写分离主要有两个过程:

第一步,根据  database.php  配置,创建写库和读库的链接  connection

第二步,调用  select  时先判断使用读库还是写库,而  insert/update/delete  统一使用写库

三 源码分析:根据 database.php 配置,创建写库和读库的链接 connection

主要文件:/vendor/laravel/framework/src/Illuminate/Database/Connectors/ConnectionFactory.php  来看看几个重要的函数:

  • 判断  database.php  是否配置了读写分离数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Establish a PDO connection based on the configuration.
*
* @param array $config
* @param string $name
* @return \Illuminate\Database\Connection
*/
public function make(array $config, $name = null)
{
$config = $this->parseConfig($config, $name);

// 如果配置了读写分离,则同时创建读库和写库的链接【因为写库也可以读】
if (isset($config['read'])) {
return $this->createReadWriteConnection($config);
}

// 如果没有配置,默认创建单个数据库链接
return $this->createSingleConnection($config);
}
  • 创建读库和写库的链接
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Create a single database connection instance.
*
* @param array $config
* @return \Illuminate\Database\Connection
*/
protected function createReadWriteConnection(array $config)
{
// 获取写库的配置信息,并创建链接
$connection = $this->createSingleConnection($this->getWriteConfig($config));
// 创建读库的链接
return $connection->setReadPdo($this->createReadPdo($config));
}
  • 多个读库会选择哪个呢

旧版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Get the read configuration for a read / write connection.
*
* @param array $config
* @return array
*/
protected function getReadConfig(array $config)
{
$readConfig = $this->getReadWriteConfig($config, 'read');

// 如果数组即多个读库,那么通过随机函数array_rand()挑一个,默认取第一个
if (isset($readConfig['host']) && is_array($readConfig['host'])) {
$readConfig['host'] = count($readConfig['host']) > 1
? $readConfig['host'][array_rand($readConfig['host'])]
: $readConfig['host'][0];
}
return $this->mergeReadWriteConfig($config, $readConfig);
}

新版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* Get the read configuration for a read / write connection.
*
* @param array $config
* @return array
*/
protected function getReadConfig(array $config)
{
return $this->mergeReadWriteConfig(
$config, $this->getReadWriteConfig($config, 'read')
);
}

/**
* Merge a configuration for a read / write connection.
*
* @param array $config
* @param array $merge
* @return array
*/
protected function mergeReadWriteConfig(array $config, array $merge)
{
return Arr::except(array_merge($config, $merge), ['read', 'write']);
}

/**
* Get a read / write level configuration.
*
* @param array $config
* @param string $type
* @return array
*/
protected function getReadWriteConfig(array $config, $type)
{
return isset($config[$type][0])
? Arr::random($config[$type])
: $config[$type];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
class Arr
{
...

/**
* Get all of the given array except for a specified array of keys.
*
* @param array $array
* @param array|string $keys
* @return array
*/
public static function except($array, $keys)
{
static::forget($array, $keys);

return $array;
}

...

/**
* Remove one or many array items from a given array using "dot" notation.
*
* @param array $array
* @param array|string $keys
* @return void
*/
public static function forget(&$array, $keys)
{
$original = &$array;

$keys = (array) $keys;

if (count($keys) === 0) {
return;
}

foreach ($keys as $key) {
// if the exact key exists in the top-level, remove it
if (static::exists($array, $key)) {
unset($array[$key]);

continue;
}

$parts = explode('.', $key);

// clean up before each pass
$array = &$original;

while (count($parts) > 1) {
$part = array_shift($parts);

if (isset($array[$part]) && is_array($array[$part])) {
$array = &$array[$part];
} else {
continue 2;
}
}

unset($array[array_shift($parts)]);
}
}

...

/**
* Get one or a specified number of random values from an array.
*
* @param array $array
* @param int|null $number
* @return mixed
*
* @throws \InvalidArgumentException
*/
public static function random($array, $number = null)
{
$requested = is_null($number) ? 1 : $number;

$count = count($array);

if ($requested > $count) {
throw new InvalidArgumentException(
"You requested {$requested} items, but there are only {$count} items available."
);
}

if (is_null($number)) {
return $array[array_rand($array)];
}

if ((int) $number === 0) {
return [];
}

$keys = array_rand($array, $number);

$results = [];

foreach ((array) $keys as $key) {
$results[] = $array[$key];
}

return $results;
}
}
  • 写库也是随机选择的

旧版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Get a read / write level configuration.
*
* @param array $config
* @param string $type
* @return array
*/
protected function getReadWriteConfig(array $config, $type)
{

// 如果多个,那么通过随机函数array_rand()挑一个
if (isset($config[$type][0])) {
return $config[$type][array_rand($config[$type])];
}
return $config[$type];
}

新版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Get a read / write level configuration.
*
* @param array $config
* @param string $type
* @return array
*/
protected function getReadWriteConfig(array $config, $type)
{
return isset($config[$type][0])
? Arr::random($config[$type])
: $config[$type];
}

总结

  • 可以设置多个读库和多个写库,或者不同组合,比如一个写库两个读库

  • 每次只创建一个读库链接和一个写库链接,从多个库中随机选择一个

四 源码分析:调用 select 时先判断使用读库还是写库,而 insert/update/delete 统一使用写库

主要文件:/vendor/laravel/framework/src/Illuminate/Database/Connection.php  看看几个重要的函数

  • select  函数根据第三个输入参数判断使用读库还是写库(true 使用读库,false 使用写库;默认使用读库)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* Run a select statement against the database.
*
* @param string $query
* @param array $bindings
* @param bool $useReadPdo
* @return array
*/
public function select($query, $bindings = [], $useReadPdo = true)
{
return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
if ($this->pretending()) {
return [];
}

// 根据$useReadPdo参数,判断使用读库还是写库;
// true使用读库,false使用写库;默认使用读库
// For select statements, we'll simply execute the query and return an array
// of the database result set. Each element in the array will be a single
// row from the database table, and will either be an array or objects.
$statement = $this->prepared($this->getPdoForSelect($useReadPdo)
->prepare($query));

$this->bindValues($statement, $this->prepareBindings($bindings));

$statement->execute();

return $statement->fetchAll();
});
}

/**
* Get the PDO connection to use for a select query.
*
* @param bool $useReadPdo
* @return \PDO
*/
protected function getPdoForSelect($useReadPdo = true)
{
// 根据$useReadPdo参数,选择PDO即判断使用读库还是写库;
// true使用读库getReadPdo,false使用写库getPdo;
return $useReadPdo ? $this->getReadPdo() : $this->getPdo();
}
  • insert/update/delete  统一使用写库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/**
* Run an insert statement against the database.
*
* @param string $query
* @param array $bindings
* @return bool
*/
public function insert($query, $bindings = [])
{
return $this->statement($query, $bindings);
}

/**
* Run an update statement against the database.
*
* @param string $query
* @param array $bindings
* @return int
*/
public function update($query, $bindings = [])
{
return $this->affectingStatement($query, $bindings);
}

/**
* Run a delete statement against the database.
*
* @param string $query
* @param array $bindings
* @return int
*/
public function delete($query, $bindings = [])
{
return $this->affectingStatement($query, $bindings);
}

/**
* Execute an SQL statement and return the boolean result.
*
* @param string $query
* @param array $bindings
* @return bool
*/
public function statement($query, $bindings = [])
{
return $this->run($query, $bindings, function ($query, $bindings) {
if ($this->pretending()) {
return true;
}

// 直接调用写库
$statement = $this->getPdo()->prepare($query);

$this->bindValues($statement, $this->prepareBindings($bindings));

$this->recordsHaveBeenModified();

return $statement->execute();
});
}

/**
* Run an SQL statement and get the number of rows affected.
*
* @param string $query
* @param array $bindings
* @return int
*/
public function affectingStatement($query, $bindings = [])
{
return $this->run($query, $bindings, function ($query, $bindings) {
if ($this->pretending()) {
return 0;
}

// 直接调用写库
// For update or delete statements, we want to get the number of rows affected
// by the statement and return that back to the developer. We'll first need
// to execute the statement and then we'll use PDO to fetch the affected.
$statement = $this->getPdo()->prepare($query);

$this->bindValues($statement, $this->prepareBindings($bindings));

$statement->execute();

$this->recordsHaveBeenModified(
($count = $statement->rowCount()) > 0
);

return $count;
});
}

总结:

  • getReadPdo()  获得读库链接,getPdo()  获得写库链接;

  • select()  函数根据第三个参数判断使用读库还是写库;

五 强制使用写库

有时候,我们需要读写实时一致,写完数据库后,想马上读出来,那么读写都指定一个数据库即可。 虽然 Laravel5 配置了读写分离,但也提供了另外的方法强制读写使用同一个数据库。

实现原理:上面  $this->select()  时指定使用写库的链接,即第三个参数  useReadPdo  设置为  false  即可。

有几个方法可实现:

  • 调用方法 1: DB::table('users')->selectFromWriteConnection('*')->where('id', $id)->first();

$user = DB::selectFromWriteConnection('select * from users where id=42111');

源码解释:通过  selectFromWriteConnection()  函数 主要文件:

/vendor/laravel/framework/src/Illuminate/Database/Connection.php

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Run a select statement against the database.
*
* @param string $query
* @param array $bindings
* @return array
*/
public function selectFromWriteConnection($query, $bindings = [])
{
// 上面有解释$this->select()函数的第三个参数useReadPdod的意义
// 第三个参数是 false,所以 select 时会使用写库,而不是读库
return $this->select($query, $bindings, false);
}
  • 调用方法 2: User::onWriteConnection()->find($id);

源码解释:通过  onWriteConnection()  函数 主要文件:

/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php

1
2
3
4
5
6
7
8
9
10
11
/**
* Begin querying the model on the write connection.
*
* @return \Illuminate\Database\Query\Builder
*/
public static function onWriteConnection()
{
$instance = new static;
// query builder 指定使用写库
return $instance->newQuery()->useWritePdo();
}

再看看  query builder  如何指定使用写库 主要文件:

/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Use the write pdo for query.
*
* @return $this
*/
public function useWritePdo()
{
// 指定使用写库,useWritePdo 为true
$this->useWritePdo = true;

return $this;
}

/**
* Run the query as a "select" statement against the connection.
*
* @return array
*/
protected function runSelect()
{
// 执行select时,useWritePdo原值为true,这里取反,被改成false;
// 即$this->select()函数第三个参数为false,所以使用写库;
return $this->connection->select($this->toSql(), $this->getBindings(), ! $this->useWritePdo);
}

开启日志验证

使用 mysql general log 来验证数据库读写分离

主数据库开启 general log

1
2
3
4
5
6
7
8
9
10
11
mysql> show global variables like '%general%';
+------------------+------------------------------------------------------+
| Variable_name | Value |
+------------------+------------------------------------------------------+
| general_log | OFF |
| general_log_file | D:\soft\phpstudy\PHPTutorial\MySQL\data\admin-PC.log |
+------------------+------------------------------------------------------+
2 rows in set (0.00 sec)

mysql> set global general_log = on;
Query OK, 0 rows affected (0.05 sec)

从数据库开启 general log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mysql> show global variables like '%general%';
+------------------+-----------------------------------------------------------+

| Variable_name | Value |

+------------------+-----------------------------------------------------------+

| general_log | OFF |

| general_log_file | D:\soft\mysql5.7.24\mysql-5.7.24-winx64\data\admin-PC.log |

+------------------+-----------------------------------------------------------+

2 rows in set, 1 warning (0.00 sec)

mysql> set global general_log = on;
Query OK, 0 rows affected (0.04 sec)

关闭日志

1
2
mysql> set global general_log = off;
Query OK, 0 rows affected (0.01 sec)
1
2
3
4
// 指定log路径
// set global general_log_file='/tmp/general.lg'; #设置路径
// 也可以将日志记录在表中
// set global log_output='table' // 可以在mysql数据库下查找 general_log表

问题

问题 1

写入了数据,但查询时却报 No query result ,而且只是偶然性出现,没啥规律。

解决方案

初以为是 prettus/l5-repository 包的缓存引起的,但关掉它的缓存功能后问题依旧。后来好一阵折腾,直到再一次仔细翻看文档, 才发现 Laravel5.5 数据库读写分离配置的部分额外提到了一个 sticky 项,文档里这部分原文如下:

The sticky Option

The sticky option is an optional value that can be used to allow the immediate reading of records that have been written to the database during the current request cycle. If the sticky option is enabled and a “write” operation has been performed against the database during the current request cycle, any further “read” operations will use the “write” connection. This ensures that any data written during the request cycle can be immediately read back from the database during that same request. It is up to you to decide if this is the desired behavior for your application.

在没有启用  sticky  的时候,使用  write  连接写入数据后立即读取,读取时使用的是  read  连接,这样就有可能出问题。将  sticky  设置为  true  后,在与这个写入操作相同的请求周期内的后续读取操作,仍然使用原来的  write  连接。

或者  强制使用写库

参考资料:

Laravel 5 配置读写分离和源码分析

Laravel 5 配置数据库主从读写分离和源码分析

注解 & Aspect

这个图是基于丝路项目的流程逻辑, 并不和下边的类相符, 下边的类是用 Hyperf 原生进行定义的

注解

Class 注解

注解是将一系列的数据进行关联的一种方式

类的注解以及生成的数据(全部)

1
2
3
4
5
6
7
8
9
10
11
12
array(2) {
["App\Service\AnClass\ClassAllIn"]=>
object(App\Annotation\AnClass)#25 (1) {
["name"]=>
string(5) "allin"
}
["App\Service\AnClass\ClassQ"]=>
object(App\Annotation\AnClass)#49 (1) {
["name"]=>
string(1) "q"
}
}

这里有一个注解类, 两个注解实现类
这里表明注册在 class 中可用, 传入的参数代表可以将参数初始化到 公共变量中, 在变量初始化的时候是进行如此设定的

1
2
3
4
5
6
7
8
9
10
11
12
13
# file : AbstractAnnotation.php

# 初始化
public function __construct($value = null)
{
if (is_array($value)) {
foreach ($value as $key => $val) {
if (property_exists($this, $key)) {
$this->{$key} = $val;
}
}
}
}
1
2
3
4
5
6
7
8
9
# file : ClassQ.php

# 使用
/**
* @AnClass(name="q")
*/
class ClassQ
{
}

注解的传参 : Url : https://hyperf.wiki/2.1/#/zh-cn/annotation?id=%E6%B3%A8%E8%A7%A3%E5%8F%82%E6%95%B0%E4%BC%A0%E9%80%92

  • 传递主要的单个参数 @DemoAnnotation("value")
  • 传递字符串参数 @DemoAnnotation(key1="value1", key2="value2")
  • 传递数组参数 @DemoAnnotation(key={"value1", "value2"})
1
2
3
4
5
6
7
8
/**
* @Annotation
* @Target({"CLASS"})
*/
class AnClass extends AbstractAnnotation
{
public string $name = '';
}

property (属性)

将属性/方法/定义传参

1
2
3
4
5
6
7
8
9
10
[
[
["class"]=> "App\Service\Annotation\PropertyAllIn"
["property"]=> "actionName"
["annotation"]=>
object(App\Annotation\AnProperty)#22 (1) {
["name"]=> "allin"
}
]
]

Method(方法)

1
2
3
4
5
6
7
8
9
[
"App\Annotation\AnMethodByParams"=>
object(App\Annotation\AnMethodByParams)#24 (2) {
["method"]=> "allin"
["params"]=> [
[0]=> string(2) "id"
]
}
]

注解的分类

切面单独独立出来

@Annotation

注解的标识

@Target

注解的解析位置 CLASS, METHOD, PROPERTY, ALL

@Constants

常量数据

@Inject

标记属性, Hyperf 会自动注入对应的对象和值

@AutoController 以及控制器相关的代码

路由的注解
@Controller : 表明当前类是一个控制器类
@RequestMapping(path=”index”, methods=”get,post”) : 定义路由访问, 路径是控制器 + 当前定义的 path
@GetMapping : Get 方法
@PostMapping : Post
@PutMapping : Put
@PatchMapping : Patch 方法(对资源进行部分修改)
@DeleteMapping : Delete 删除

@Listener

切面 (Aspect)

通过动态代理等技术实现程序功能的统一维护的一种技术.
使用 Aop 扩展业务逻辑, 并使耦合度降低, 提高程序的可重用性

Socket

@SocketIONamespace(“/“)

socket 命名空间定义, 这里的命名空间和 socket.io 指定的相符, 也可以通过相关的路由来添加

@Event

以方法名作为事件名来分发事件, 如果写在控制器中则自动进行方法的映射.

[译] 在 web 应用中集成 chrome 的 clockwork 插件

原文地址: Integrating Chrome’s Clockwork into your web-app

在 开发者工具 中查看 clockwork 插件

chrome clockwork 下载地址

Clockwork 是个很帅的工具, 他能运行在任何服务端的平台/框架, 我会带你在你的 php 应用中集成这个插件.

Part.1 安装

使用 Composer 安装

  • composer.json 添加 “itsgoingd/clockwork”: “dev-master”
  • 运行 composer.phar update更新

下载安装
作为另外一种替代方式, 你可以使用最古老的方式来下载 或者 克隆 [https://github.com/itsgoingd/clockwork/](https://github.com/itsgoingd/clockwork/) 来安装

Part.2: 和 Web App 集成

讲这个函数库放在你的项目的调用的范围内, 最好和 controller/router 放到一块, header()方法需要在未输出任何内容的前提下使用这个函数, 这个方法会告知 Clockwork 插件来向服务端请求 clockwork 数据.

最后两行 resolveRequest()storeRequest() 必须在你 的 web app 执行完成之后运行, 否则不会出现完整的调用日志, 每个 DataSource 都需要有 resolve() 方法, 这个方法在每个数据源执行的时候进行调用. 这个会分析和保存所有的执行时间, 查询, 日志来供 Clockwork 调用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// this sends out headers, so unless you are buffering output
// you may want to place this early in your app
$clockwork = new Clockwork();
header("X-Clockwork-Id: " . $clockwork->getRequest()->id);
header("X-Clockwork-Version: " . Clockwork::VERSION);

// spawn your own custom DataSource (explained below)
$clockwork->addDataSource(new YourCustomDataSource($yourAppContext));

// attach a sample datasource that comes with
// the Clockwork library (grabs session info, etc)
$clockwork->addDataSource(new PhpDataSource());

// we could write a custom APC, MemCached, etc here
// or just use the FileStorage that comes with Clockwork
$clockwork->setStorage(new FileStorage("some/path/"));

// run your web-app here
// ...

// once your app is done, tell Clockwork to resolve and store
// data in a file on your server; it will call the resolve()
// method on YourCustomDataSource and PhpDataSource
$clockwork->resolveRequest();
$clockwork->storeRequest();

这会在 some/path/ 目录下创建一个新文件.
在页面加载完成之后, Chrome 的  Clockwork 扩展获取 X-Clockwork 的头信息, 并且向服务器发送一条请求来获取生成的文件, 这个文件的 id 值会存在服务器返回的 header 中, 这个 header 的标识符是 X-Clockwork-Id.

例如: 如果 X-Clockwork-Id1387208177.8923.1394938488, 然后 Chrome 的 Clockwork 扩展会请求 /__clockwork/1387208177.8923.1394938488 这个地址来获取服务器存储的 json 文件.

你可以通过处理 /__clockwork/[*:id] 这个请求来返回这个插件所需要的数据.

1
2
3
$storage = new FileStorage("/some/path/");
$data = $storage->retrieve($ctx->parameters->id);
$data->toJson();

Klein 或者 AltoRouter 路由类能够处理类似于以上的语法, 并且通过控制器文件来获取 request-id, 否则的话你可以通过 $_SERVER[‘REQUEST_URI’] 来手动的获取 request-id , 然后 FileStorage 将会处理后边的事情.

Part.3: 自定义数据源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class YourCustomDataSource extends DataSource
{
protected $context;

/**
* YourAppContext should link to your main web-app
* and fetch timings, queries, and logs
*/
function __construct(YourAppContext $context)
{
$this->context = $context;
}

/**
* the entry-point. called by Clockwork itself.
*/
function resolve(Request $request)
{
$timings = $this->context->getTimings();

// optionally: pre-sort the timeline
uasort($timeline, function($a, $b) {
if($a['start'] > $b['start'])
return 1;

if($a['start'] == $b['start']) {
if($a['end'] > $b['end'])
return 1;
elseif ($a['end'] < $b['end'])
return -1;

return 0;
}

return -1;
});

$queries = $this->context->getQueries();

$request->timelineData = $timeline;
$request->databaseQueries = $queries;

return $request;
}
}

例如:

1
$timings[0] = ['start' => 1387208058.1, 'end' => 1387208058.5, 'duration' => 40, 'description' => 'parsing tweets']

startend 标签使用 秒 作为单位, duration 使用毫秒作为单位,  startend 标签的时间乘以 1000 ($_SERVER[‘REQUEST_TIME_FLOAT’] * 1000) . 所以使用 microtime(true) 作为时间相对单位好于从 0 开始

1
$queries[0] = ['query' => "SELECT awesomeness FROM cereals WHERE name = `Captain Crunch`", 'duration' => 13]

这里放了一条查询数据库的语句, 仅供参考, 时间以毫秒记.

更多开发见 clockwork development-notes

Url 重写

这里使用了路由机制使插件访问服务器, 而且地址和请求方式都是写好在 代码中的, 所以需要在服务器中配置重写, 对于不是单入口和不支持路由访问的来说, 需要配置 url 重写
Nginx:

1
2
3
4
5
server{
...
rewrite ^/__clockwork/(.*)$ somefile.php?id=$1
...
}

Apache: vhosts 模式

1
2
3
4
5
6
7
8
9
<VirtualHost *:80>
ServerAdmin admin@qq.com
DocumentRoot "/var/www/project"
ServerName project.test.com
ErrorLog "logs/project.test.com-error.log"
CustomLog "logs/project.test.com-access.log" common
RewriteEngine on
RewriteRule ^/__clockwork/(.*)$ /extend/debug.php?action=cw&id=$1
</VirtualHost>

译者补充: Laravel 又一个调试利器 anbu

Laravel - 解析 - 加载机制

公共文件入口

public/index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
bootstrap/autoload.php
自动加载机制
vendor/autoload.php
加载 composer 扩展包的自动加载机制
storage/framework/compiled.php
如果存在编译文件则加载编译文件

bootstrap/app.php
创建 `$app` = new Illuminate\Foundation\Application
注册基础绑定 registerBaseBindings
实例化 app
实例化 Illuminate\Container\Container
注册基础服务绑定 registerBaseServiceProviders
注册 事件绑定
注册 路由绑定
注册核心容器 alias 关联 registerCoreContainerAliases
App\Http\Kernel
App\Console\Kernel
App\Exceptions\Handler

kernel 的处理
- 创建 `kernel`
- 创建 `response`
- 发出 `response`
- 终止 `kernel`

shopex485 文件树/Tree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
D:\WAMP\WWW\SHOPEX
│ api.php
│ error.html 错误文件
│ favicon.ico ico
│ index.php 首页入口
│ install.txt
│ passport_client.php
│ robots.txt
│ root.htaccess
│ version.txt
│ web.config.sample

├─api
│ helper.php
│ uc.php

├─config 配置文件
│ config.php 基本配置文件
│ config.sample.php
│ install.lock 安装识别

├─core 核心文件
│ │ func_ext.php
│ │ kernel.php
│ │ site.xml
│ │ version.txt
│ │
│ ├─admin
│ │ ├─controller
│ │ │ │ ctl.cent_save.php
│ │ │ │ ctl.dashboard.php
│ │ │ │ ctl.default.php
│ │ │ │ ctl.demo.php
│ │ │ │ ctl.editor.php
│ │ │ │ ctl.passport.php
│ │ │ │ ctl.sfile.php
│ │ │ │
│ │ │ ├─admin
│ │ │ │ ctl.operator.php
│ │ │ │ ctl.profile.php
│ │ │ │ ctl.roles.php
│ │ │ │
│ │ │ ├─content
│ │ │ │ ctl.articles.php
│ │ │ │ ctl.content.php
│ │ │ │ ctl.custommessage.php
│ │ │ │ ctl.frendlink.php
│ │ │ │ ctl.menus.php
│ │ │ │ ctl.pages.php
│ │ │ │ ctl.sitemaps.php
│ │ │ │
│ │ │ ├─distribution
│ │ │ │ ctl.autosync.php
│ │ │ │ ctl.supplier.php
│ │ │ │
│ │ │ ├─goods
│ │ │ │ ctl.adjunct.php
│ │ │ │ ctl.brand.php
│ │ │ │ ctl.category.php
│ │ │ │ ctl.discuss.php
│ │ │ │ ctl.gnotify.php
│ │ │ │ ctl.gtype.php
│ │ │ │ ctl.items.php
│ │ │ │ ctl.package.php
│ │ │ │ ctl.product.php
│ │ │ │ ctl.spec.php
│ │ │ │ ctl.specification.php
│ │ │ │ ctl.virtualcat.php
│ │ │ │
│ │ │ ├─member
│ │ │ │ ctl.advance.php
│ │ │ │ ctl.gask.php
│ │ │ │ ctl.level.php
│ │ │ │ ctl.member.php
│ │ │ │ ctl.memberattr.php
│ │ │ │ ctl.messenger.php
│ │ │ │ ctl.messenger0.php
│ │ │ │ ctl.msgbox.php
│ │ │ │ ctl.shopbbs.php
│ │ │ │
│ │ │ ├─order
│ │ │ │ ctl.delivery_centers.php
│ │ │ │ ctl.delivery_printer.php
│ │ │ │ ctl.order.php
│ │ │ │ ctl.payment.php
│ │ │ │ ctl.po.php
│ │ │ │ ctl.printer_center.php
│ │ │ │ ctl.refund.php
│ │ │ │ ctl.reship.php
│ │ │ │ ctl.return_product.php
│ │ │ │ ctl.shipping.php
│ │ │ │
│ │ │ ├─sale
│ │ │ │ ctl.activity.php
│ │ │ │ ctl.coupon.php
│ │ │ │ ctl.couponGenerate.php
│ │ │ │ ctl.exchangeCoupon.php
│ │ │ │ ctl.gift.php
│ │ │ │ ctl.giftcat.php
│ │ │ │ ctl.point.php
│ │ │ │ ctl.promotion.php
│ │ │ │ ctl.salescount.php
│ │ │ │ ctl.tools.php
│ │ │ │
│ │ │ ├─service
│ │ │ │ ctl.certificate.php
│ │ │ │ ctl.demo_data.php
│ │ │ │ ctl.domainbind.php
│ │ │ │ ctl.download.php
│ │ │ │ ctl.kft.php
│ │ │ │ ctl.wss.php
│ │ │ │
│ │ │ ├─system
│ │ │ │ ctl.about.php
│ │ │ │ ctl.addon.php
│ │ │ │ ctl.appmgr.php
│ │ │ │ ctl.backup.php
│ │ │ │ ctl.comeback.php
│ │ │ │ ctl.cur.php
│ │ │ │ ctl.debug.php
│ │ │ │ ctl.location.php
│ │ │ │ ctl.magicvars.php
│ │ │ │ ctl.passport.php
│ │ │ │ ctl.setting.php
│ │ │ │ ctl.sfile.php
│ │ │ │ ctl.template.php
│ │ │ │ ctl.tmpimage.php
│ │ │ │ ctl.tools.php
│ │ │ │ ctl.trigger.php
│ │ │ │
│ │ │ └─trading
│ │ │ ctl.delivery.php
│ │ │ ctl.deliveryarea.php
│ │ │ ctl.deliverycorp.php
│ │ │ ctl.payment.php
│ │ │
│ │ ├─locale
│ │ ├─smartyplugin
│ │ │ block.area.php
│ │ │ block.help.php
│ │ │ block.role.php
│ │ │ block.tab.php
│ │ │ block.tabber.php
│ │ │ compiler.button.php
│ │ │ compiler.finder.php
│ │ │ compiler.finder_lister.php
│ │ │ compiler.img.php
│ │ │ function.filter.php
│ │ │ function.finder.php
│ │ │ function.json.php
│ │ │ function.pager.php
│ │ │ function.refer.php
│ │ │ function.setting.php
│ │ │ function.tag.php
│ │ │ function.template_filter.php
│ │ │ function.toinput.php
│ │ │ function.uploader.php
│ │ │ input.object.php
│ │ │ modifier.b2bcur.php
│ │ │ modifier.barcode.php
│ │ │ modifier.region.php
│ │ │ modifier.size.php
│ │ │
│ │ └─view
│ │ │ appTaobaoIntro.html
│ │ │ blank.html
│ │ │ dashboard.html
│ │ │ helper.html
│ │ │ index.html
│ │ │ login.html
│ │ │ menuSearch.html
│ │ │ page.html
│ │ │ paymentpage.html
│ │ │ print.html
│ │ │ regionSelect.html
│ │ │ reglic.html
│ │ │ sidemenu.html
│ │ │ singlepage.html
│ │ │ status.html
│ │ │ treeNode.html
│ │ │
│ │ ├─admin
│ │ │ finder_action.html
│ │ │ finder_command.html
│ │ │ finder_filter.html
│ │ │ op_detail.html
│ │ │ op_edit.html
│ │ │ op_login.html
│ │ │ roles_action.html
│ │ │ roles_cmd.html
│ │ │ roles_item.html
│ │ │ self.html
│ │ │
│ │ ├─content
│ │ │ │ custompage.html
│ │ │ │ definedDetail.html
│ │ │ │ editHtml.html
│ │ │ │ frameset.html
│ │ │ │ goodscat.html
│ │ │ │ layout.html
│ │ │ │ menus.html
│ │ │ │ menusDetail.html
│ │ │ │ menusDetailAdd.html
│ │ │ │ menusDetailEdit.html
│ │ │ │ newnode.html
│ │ │ │ node_info.html
│ │ │ │ page.html
│ │ │ │ page_edit.html
│ │ │ │ page_frame.html
│ │ │ │ page_url.html
│ │ │ │ sitemap.html
│ │ │ │ type_edit.html
│ │ │ │ type_list.html
│ │ │ │ type_new.html
│ │ │ │ welcome.html
│ │ │ │
│ │ │ ├─article
│ │ │ │ article.html
│ │ │ │ finder_action.html
│ │ │ │ finder_filter.html
│ │ │ │ getgoods.html
│ │ │ │ preview.html
│ │ │ │
│ │ │ ├─custommessage
│ │ │ │ edit.html
│ │ │ │ finder_command.html
│ │ │ │
│ │ │ ├─frendlink
│ │ │ │ detail.html
│ │ │ │ finder_action.html
│ │ │ │
│ │ │ └─widgets
│ │ │ htmlbox.html
│ │ │ info.html
│ │ │ list.html
│ │ │ widgetsCenter.html
│ │ │ widgetsDetailRight.html
│ │ │ widgetsLeftDetail.html
│ │ │
│ │ ├─delivery
│ │ │ area_edit.html
│ │ │ area_finder_action.html
│ │ │ area_list.html
│ │ │ area_map.html
│ │ │ area_new.html
│ │ │ area_sub_treeList.html
│ │ │ area_treeList.html
│ │ │ check_exp.html
│ │ │ command.html
│ │ │ corp_edit.html
│ │ │ corp_finder_action.html
│ │ │ corp_list.html
│ │ │ corp_new.html
│ │ │ dtype_edit.html
│ │ │ finder_action.html
│ │ │ finder_command.html
│ │ │ help_mantes.html
│ │ │ help_rate.html
│ │ │
│ │ ├─distribution
│ │ │ │ autosync_edit.html
│ │ │ │ autosync_local_op_row.html
│ │ │ │ autosync_pline_list.html
│ │ │ │ autosync_rule_row.html
│ │ │ │ auto_command.html
│ │ │ │ auto_finder_action.html
│ │ │ │ data_sync.html
│ │ │ │ data_sync_list.html
│ │ │ │ generalize.html
│ │ │ │ index.html
│ │ │ │ product_line.html
│ │ │ │ supplier_list.html
│ │ │ │ sync_complete.html
│ │ │ │
│ │ │ └─goods
│ │ │ goods_info.html
│ │ │ goods_type_info.html
│ │ │
│ │ ├─editor
│ │ │ body.html
│ │ │ dlg_flash.html
│ │ │ dlg_image.html
│ │ │ dlg_lnk.html
│ │ │ dlg_mce.html
│ │ │ dlg_result.html
│ │ │ dlg_table.html
│ │ │ gallery_img.html
│ │ │ gallery_swf.html
│ │ │ object_items.html
│ │ │ object_selector.html
│ │ │ style_1.html
│ │ │ style_2.html
│ │ │ the_filter.html
│ │ │
│ │ ├─finder
│ │ │ browser.html
│ │ │ cell_editor.html
│ │ │ col_setting.html
│ │ │ common.html
│ │ │ compact.html
│ │ │ detail-in-one.html
│ │ │ detail.html
│ │ │ detail_title.html
│ │ │ export.html
│ │ │ filter.html
│ │ │ filter_show.html
│ │ │ finder-tag.html
│ │ │ import.html
│ │ │ input-row.html
│ │ │ input.html
│ │ │ list-error.html
│ │ │ list.html
│ │ │ lister.html
│ │ │ pvfilter.html
│ │ │ recycleCommon.html
│ │ │ recycleCompact.html
│ │ │ result.html
│ │ │ rowonly.html
│ │ │ rows.html
│ │ │
│ │ ├─member
│ │ │ │ advancelist.html
│ │ │ │ advance_finder_action.html
│ │ │ │ advance_list.html
│ │ │ │ batch_edit.html
│ │ │ │ finder_action.html
│ │ │ │ finder_command.html
│ │ │ │ finder_filter.html
│ │ │ │ member_edit.html
│ │ │ │ member_items.html
│ │ │ │ member_new.html
│ │ │ │ member_ordertab.html
│ │ │ │ modify_experience.html
│ │ │ │ modify_point.html
│ │ │ │ remark.html
│ │ │ │ remark_row.html
│ │ │ │ review_info.html
│ │ │ │ sub_discuss.html
│ │ │ │ sub_edit.html
│ │ │ │ sub_ext_info.html
│ │ │ │ sub_message.html
│ │ │ │ sub_orders.html
│ │ │ │ sub_password.html
│ │ │ │ sub_point_history.html
│ │ │ │ sub_review.html
│ │ │ │
│ │ │ ├─attr
│ │ │ │ attr_edit.html
│ │ │ │ attr_new.html
│ │ │ │ map.html
│ │ │ │
│ │ │ ├─gask
│ │ │ │ detail.html
│ │ │ │ finder_action.html
│ │ │ │ finder_filter.html
│ │ │ │ setting.html
│ │ │ │
│ │ │ ├─level
│ │ │ │ finder_action.html
│ │ │ │ level_edit.html
│ │ │ │ level_new.html
│ │ │ │
│ │ │ ├─msgbox
│ │ │ │ msg_command.html
│ │ │ │ msg_html_items.html
│ │ │ │ msg_items.html
│ │ │ │ msg_update_revert.html
│ │ │ │
│ │ │ └─shopbbs
│ │ │ msg_html_items.html
│ │ │ msg_items.html
│ │ │ msg_update_revert.html
│ │ │ setting.html
│ │ │
│ │ ├─messenger
│ │ │ config.html
│ │ │ edtmpl.html
│ │ │ index.html
│ │ │ outbox.html
│ │ │ page.html
│ │ │ queue.html
│ │ │ testemail.html
│ │ │ write.html
│ │ │
│ │ ├─order
│ │ │ │ actbar.html
│ │ │ │ detail_title.html
│ │ │ │ dly_center.html
│ │ │ │ dly_center_action.html
│ │ │ │ dly_center_command.html
│ │ │ │ dly_center_editor.html
│ │ │ │ dly_printer_action.html
│ │ │ │ dly_printer_command.html
│ │ │ │ dly_printer_editor.html
│ │ │ │ dly_printer_import.html
│ │ │ │ dly_printer_uploadbg.html
│ │ │ │ edit_items.html
│ │ │ │ edit_local_items.html
│ │ │ │ edit_po.html
│ │ │ │ finder_action.html
│ │ │ │ finder_command.html
│ │ │ │ finder_filter.html
│ │ │ │ index.html
│ │ │ │ make_po.html
│ │ │ │ new_items.html
│ │ │ │ new_pkgitems.html
│ │ │ │ od_bill.html
│ │ │ │ od_delivery.html
│ │ │ │ od_items.html
│ │ │ │ od_logs.html
│ │ │ │ od_mark.html
│ │ │ │ od_msg.html
│ │ │ │ od_pmts.html
│ │ │ │ orderconsign.html
│ │ │ │ orderpayed.html
│ │ │ │ orderrefund.html
│ │ │ │ orderreturn.html
│ │ │ │ order_create.html
│ │ │ │ order_detail.html
│ │ │ │ order_edit.html
│ │ │ │ order_flow.html
│ │ │ │ order_info.html
│ │ │ │ order_membertab.html
│ │ │ │ order_new.html
│ │ │ │ order_prom.html
│ │ │ │ order_remark.html
│ │ │ │ page.html
│ │ │ │ po_delivery.html
│ │ │ │ po_detail.html
│ │ │ │ po_items.html
│ │ │ │ po_items_local.html
│ │ │ │ po_items_supplier.html
│ │ │ │ po_pay.html
│ │ │ │ print.html
│ │ │ │ printertest.html
│ │ │ │ printstyle.html
│ │ │ │ print_cart.html
│ │ │ │ print_dly.html
│ │ │ │ print_dly_job.html
│ │ │ │ print_sheet.html
│ │ │ │ show_order_msg.html
│ │ │ │
│ │ │ ├─payment
│ │ │ │ detail.html
│ │ │ │ finder_filter.html
│ │ │ │
│ │ │ ├─refund
│ │ │ │ detail.html
│ │ │ │ finder_action.html
│ │ │ │ finder_filter.html
│ │ │ │
│ │ │ ├─reship
│ │ │ │ detail.html
│ │ │ │
│ │ │ ├─return_product
│ │ │ │ detail.html
│ │ │ │ filter.html
│ │ │ │ return_status.html
│ │ │ │
│ │ │ └─shipping
│ │ │ detail.html
│ │ │
│ │ ├─passport
│ │ │ passport_edit.html
│ │ │ passport_list.html
│ │ │ passport_ucenter.html
│ │ │
│ │ ├─payment
│ │ │ finder_action.html
│ │ │ finder_command.html
│ │ │ pay_detail.html
│ │ │ pay_edit.html
│ │ │ pay_index.html
│ │ │ pay_index_china.html
│ │ │ pay_index_other.html
│ │ │ pay_list.html
│ │ │ pay_new.html
│ │ │
│ │ ├─po
│ │ │ actbar.html
│ │ │ command.html
│ │ │ detail.html
│ │ │ detail_info.html
│ │ │ detail_money.html
│ │ │ detail_ship.html
│ │ │ detail_title.html
│ │ │ filter.html
│ │ │ order_items.html
│ │ │
│ │ ├─product
│ │ │ │ batchEdit.html
│ │ │ │ batchEditBrand.html
│ │ │ │ batchEditBrief.html
│ │ │ │ batchEditCat.html
│ │ │ │ batchEditDifferencePrice.html
│ │ │ │ batchEditDifferencePriceList.html
│ │ │ │ batchEditDifferenceStore.html
│ │ │ │ batchEditDifferenceStoreList.html
│ │ │ │ batchEditDorder.html
│ │ │ │ batchEditName.html
│ │ │ │ batchEditScore.html
│ │ │ │ batchEditUniformPrice.html
│ │ │ │ batchEditUniformStore.html
│ │ │ │ batchEditWeight.html
│ │ │ │ batchImage.html
│ │ │ │ detail.html
│ │ │ │ filter_addon.html
│ │ │ │ finder_action.html
│ │ │ │ finder_command.html
│ │ │ │ finder_filter.html
│ │ │ │ finder_products_action.html
│ │ │ │ finder_products_filter.html
│ │ │ │ gimage.html
│ │ │ │ gimage_goods.html
│ │ │ │ import.html
│ │ │ │ levelPrice.html
│ │ │ │ list_normal.html
│ │ │ │ nospec.html
│ │ │ │ ratelist.html
│ │ │ │ sel_spec_value.html
│ │ │ │ spec.html
│ │ │ │ spec_addcol.html
│ │ │ │ spec_addspectab.html
│ │ │ │ spec_addspecvalue.html
│ │ │ │ spec_row.html
│ │ │ │ spec_selalbumsimg.html
│ │ │ │ spec_select.html
│ │ │ │ spec_value.html
│ │ │ │ type_turn.html
│ │ │ │ workpage.html
│ │ │ │
│ │ │ ├─adjunct
│ │ │ │ filter.html
│ │ │ │ goods.html
│ │ │ │ info.html
│ │ │ │ row.html
│ │ │ │
│ │ │ ├─brand
│ │ │ │ checkbox_list.html
│ │ │ │ detail.html
│ │ │ │ finder_action.html
│ │ │ │
│ │ │ ├─category
│ │ │ │ info.html
│ │ │ │ map.html
│ │ │ │ view.html
│ │ │ │ view_row.html
│ │ │ │
│ │ │ ├─detail
│ │ │ │ adj.html
│ │ │ │ adv.html
│ │ │ │ basic.html
│ │ │ │ content.html
│ │ │ │ frame.html
│ │ │ │ notify.html
│ │ │ │ page.html
│ │ │ │ params.html
│ │ │ │ rel.html
│ │ │ │ rel_items.html
│ │ │ │ tag.html
│ │ │ │ taskman.html
│ │ │ │ taskrows.html
│ │ │ │ view_gimages.html
│ │ │ │
│ │ │ ├─discuss
│ │ │ │ detail.html
│ │ │ │ finder_action.html
│ │ │ │ finder_filter.html
│ │ │ │ list.html
│ │ │ │ setting.html
│ │ │ │
│ │ │ ├─gnotify
│ │ │ │ finder_action.html
│ │ │ │ finder_filter.html
│ │ │ │
│ │ │ ├─gtype
│ │ │ │ brand_form.html
│ │ │ │ done.html
│ │ │ │ error.html
│ │ │ │ finder_action.html
│ │ │ │ finder_command.html
│ │ │ │ form.html
│ │ │ │ minfo_form.html
│ │ │ │ name.html
│ │ │ │ newType.html
│ │ │ │ params_form.html
│ │ │ │ props_form.html
│ │ │ │ spec.html
│ │ │ │ spec_form.html
│ │ │ │ workpage.html
│ │ │ │
│ │ │ ├─package
│ │ │ │ addPackage.html
│ │ │ │ command.html
│ │ │ │ finder_action.html
│ │ │ │ finder_filter.html
│ │ │ │ pkg_items.html
│ │ │ │
│ │ │ ├─selllog
│ │ │ │ setting.html
│ │ │ │
│ │ │ ├─spec
│ │ │ │ detail.html
│ │ │ │ finder_action.html
│ │ │ │ finder_command.html
│ │ │ │
│ │ │ └─virtualcat
│ │ │ import.html
│ │ │ info.html
│ │ │ map.html
│ │ │ maptree.html
│ │ │ rows.html
│ │ │
│ │ ├─sale
│ │ │ │ welcome.html
│ │ │ │
│ │ │ ├─activity
│ │ │ │ activityInfo.html
│ │ │ │ command.html
│ │ │ │ completeActivity.html
│ │ │ │ finder_action.html
│ │ │ │ finder_filter.html
│ │ │ │ promotion.html
│ │ │ │
│ │ │ ├─count
│ │ │ │ membercount.html
│ │ │ │ salescount.html
│ │ │ │ salescountall.html
│ │ │ │ salesguide.html
│ │ │ │ visitsalecompare.html
│ │ │ │
│ │ │ ├─coupon
│ │ │ │ │ addExchange.html
│ │ │ │ │ command.html
│ │ │ │ │ couponInfo.html
│ │ │ │ │ finder_action.html
│ │ │ │ │ finder_filter.html
│ │ │ │ │ generator.html
│ │ │ │ │ list.html
│ │ │ │ │ publish.html
│ │ │ │ │ selectProduct.html
│ │ │ │ │ selectPromotionRule.html
│ │ │ │ │ writePromotionRule.html
│ │ │ │ │
│ │ │ │ ├─exchange
│ │ │ │ │ addExchange.html
│ │ │ │ │ finder_action.html
│ │ │ │ │
│ │ │ │ └─generate
│ │ │ │ finder_action.html
│ │ │ │ finder_filter.html
│ │ │ │ list.html
│ │ │ │
│ │ │ ├─gift
│ │ │ │ addGift.html
│ │ │ │ command.html
│ │ │ │ finder_action.html
│ │ │ │ finder_filter.html
│ │ │ │
│ │ │ ├─giftcat
│ │ │ │ addType.html
│ │ │ │ command.html
│ │ │ │ finder_action.html
│ │ │ │ list.html
│ │ │ │
│ │ │ ├─point
│ │ │ │ pointSetting.html
│ │ │ │
│ │ │ ├─promotion
│ │ │ │ finder_action.html
│ │ │ │ finder_filter.html
│ │ │ │ publish.html
│ │ │ │ selectProduct.html
│ │ │ │ selectPromotionRule.html
│ │ │ │ writePromotionRule.html
│ │ │ │
│ │ │ └─wltx
│ │ │ index.html
│ │ │ open.html
│ │ │ wltx_exp.html
│ │ │ wltx_exp_aply.html
│ │ │
│ │ ├─service
│ │ │ │ checkp.html
│ │ │ │ download_complete_handle.html
│ │ │ │ download_progress.html
│ │ │ │ index.html
│ │ │ │ kft.html
│ │ │ │ welcome.html
│ │ │ │ wss.html
│ │ │ │ wssframe.html
│ │ │ │
│ │ │ └─domainbind
│ │ │ add.html
│ │ │ index.html
│ │ │ result.html
│ │ │
│ │ ├─setting
│ │ │ cert.html
│ │ │ green_card.html
│ │ │ im_setting.html
│ │ │ return_product.html
│ │ │ shopping_basic.html
│ │ │ site_basic.html
│ │ │ site_watermark.html
│ │ │ welcome.html
│ │ │
│ │ ├─splash
│ │ │ failed.html
│ │ │ notice.html
│ │ │ success.html
│ │ │
│ │ ├─system
│ │ │ ├─addons
│ │ │ │ finder_action.html
│ │ │ │ page.html
│ │ │ │ plugin-dataio.html
│ │ │ │ plugin-functions.html
│ │ │ │ plugin-messenger.html
│ │ │ │ plugin-passport.html
│ │ │ │ plugin-payment.html
│ │ │ │ plugin-pmtScheme.html
│ │ │ │ plugin-schema.html
│ │ │ │ plugin-shipping.html
│ │ │ │ widgets.html
│ │ │ │
│ │ │ ├─appmgr
│ │ │ │ app_online.html
│ │ │ │ detail.html
│ │ │ │ index.html
│ │ │ │ install.html
│ │ │ │ setting.html
│ │ │ │ uninstall.html
│ │ │ │ update.html
│ │ │ │ view_update_info.html
│ │ │ │
│ │ │ ├─backup
│ │ │ │ backup.html
│ │ │ │
│ │ │ ├─comeback
│ │ │ │ comeback.html
│ │ │ │ tgzFileList.html
│ │ │ │
│ │ │ ├─cur
│ │ │ │ cur.html
│ │ │ │ curDetail.html
│ │ │ │
│ │ │ ├─debug
│ │ │ │ clear.html
│ │ │ │ databasecheck.html
│ │ │ │ debug.html
│ │ │ │
│ │ │ ├─location
│ │ │ │ index.html
│ │ │ │
│ │ │ ├─magicvars
│ │ │ │ finder_action.html
│ │ │ │ finder_command.html
│ │ │ │ var_item.html
│ │ │ │
│ │ │ ├─tags
│ │ │ │ list.html
│ │ │ │
│ │ │ ├─template
│ │ │ │ doAddWidgets.html
│ │ │ │ edit.html
│ │ │ │ editor.html
│ │ │ │ htmEdit.html
│ │ │ │ list.html
│ │ │ │ map.html
│ │ │ │ saveWidgets.html
│ │ │ │ templateEdit.html
│ │ │ │ templateImage.html
│ │ │ │ templateSource.html
│ │ │ │ template_filter.html
│ │ │ │ theme.xml
│ │ │ │ themeupload.html
│ │ │ │ tplresource.html
│ │ │ │ widgetHeader.html
│ │ │ │ widgetsCenter.html
│ │ │ │ widgetsDetailRight.html
│ │ │ │ widgetsLeftDetail.html
│ │ │ │
│ │ │ ├─tools
│ │ │ │ about.html
│ │ │ │ article.html
│ │ │ │ brand.html
│ │ │ │ cachemgr.html
│ │ │ │ createlink.html
│ │ │ │ dbinfo.html
│ │ │ │ errorpage.html
│ │ │ │ float.html
│ │ │ │ footEdit.html
│ │ │ │ goods.html
│ │ │ │ homepage.html
│ │ │ │ list.html
│ │ │ │ searchempty.html
│ │ │ │ selectcsv.html
│ │ │ │ seo.html
│ │ │ │ sitemaps.html
│ │ │ │ svinfo.html
│ │ │ │ uploader.html
│ │ │ │ welcome.html
│ │ │ │
│ │ │ └─trigger
│ │ │ action_row.html
│ │ │ command.html
│ │ │ filter.html
│ │ │ finder_action.html
│ │ │ item.html
│ │ │ normal_trigger.html
│ │ │ system_trigger.html
│ │ │
│ │ └─upgrade
│ │ done.html
│ │ missing.html
│ │ page.html
│ │ ready.html
│ │ uploading.html
│ │
│ ├─api
│ │ │ shop_api.php
│ │ │ shop_api_model_object.php
│ │ │ shop_api_object.php
│ │ │
│ │ ├─gimage
│ │ │ ├─1.0
│ │ │ │ api_b2b_1_0_gimage.php
│ │ │ │
│ │ │ └─application_error
│ │ │ gimage_application_error.php
│ │ │
│ │ ├─goods
│ │ │ ├─1.0
│ │ │ │ api_1_0_goods.php
│ │ │ │ api_b2b_1_0_goods.php
│ │ │ │
│ │ │ ├─1.1
│ │ │ │ api_b2b_1_1_goods.php
│ │ │ │
│ │ │ ├─2.0
│ │ │ │ api_b2b_2_0_goods.php
│ │ │ │
│ │ │ ├─3.0
│ │ │ │ api_b2b_3_0_goods.php
│ │ │ │
│ │ │ └─application_error
│ │ │ b2b_goods_application_error.php
│ │ │ goods_application_error.php
│ │ │
│ │ ├─include
│ │ │ api_error_handle.php
│ │ │ api_link.php
│ │ │ api_utility.php
│ │ │
│ │ ├─member
│ │ │ ├─1.0
│ │ │ │ api_b2b_1_0_advance.php
│ │ │ │ api_b2b_1_0_member.php
│ │ │ │
│ │ │ ├─2.0
│ │ │ │ api_b2b_2_0_advance.php
│ │ │ │ api_b2b_2_0_member.php
│ │ │ │
│ │ │ ├─3.0
│ │ │ │ api_b2b_3_0_member.php
│ │ │ │
│ │ │ └─application_error
│ │ │ b2b_advance_application_error.php
│ │ │ b2b_member_application_error.php
│ │ │ member_application_error.php
│ │ │
│ │ ├─order
│ │ │ ├─1.0
│ │ │ │ api_1_0_order.php
│ │ │ │ api_b2b_1_0_delivery.php
│ │ │ │ api_b2b_1_0_order.php
│ │ │ │ api_b2b_1_0_refund.php
│ │ │ │ api_b2c_1_0_delivery.php
│ │ │ │
│ │ │ ├─2.0
│ │ │ │ api_b2b_2_0_order.php
│ │ │ │ api_b2b_2_0_refund.php
│ │ │ │ api_b2c_2_0_delivery.php
│ │ │ │
│ │ │ ├─3.0
│ │ │ │ api_b2b_3_0_order.php
│ │ │ │
│ │ │ ├─3.1
│ │ │ │ api_b2b_3_1_order.php
│ │ │ │
│ │ │ └─application_error
│ │ │ b2b_order_application_error.php
│ │ │ b2b_refund_application_error.php
│ │ │ order_application_error.php
│ │ │
│ │ ├─payment
│ │ │ ├─1.0
│ │ │ │ api_b2b_1_0_payment.php
│ │ │ │ api_b2b_1_0_payment_cfg.php
│ │ │ │
│ │ │ ├─2.0
│ │ │ │ api_b2b_2_0_payment.php
│ │ │ │ api_b2b_2_0_payment_cfg.php
│ │ │ │
│ │ │ └─application_error
│ │ │ b2b_payment_application_error.php
│ │ │ b2b_payment_cfg_application_error.php
│ │ │ payment_application_error.php
│ │ │
│ │ ├─product
│ │ │ ├─1.0
│ │ │ │ api_1_0_product.php
│ │ │ │ api_b2b_1_0_product.php
│ │ │ │
│ │ │ ├─2.0
│ │ │ │ api_b2b_2_0_product.php
│ │ │ │
│ │ │ ├─3.0
│ │ │ │ api_b2b_3_0_product.php
│ │ │ │
│ │ │ └─application_error
│ │ │ b2b_product_application_error.php
│ │ │ product_application_error.php
│ │ │
│ │ ├─productline
│ │ │ ├─1.0
│ │ │ │ api_b2b_1_0_productline.php
│ │ │ │
│ │ │ ├─2.0
│ │ │ │ api_b2b_2_0_productline.php
│ │ │ │
│ │ │ └─application_error
│ │ │ b2b_productline_application_error.php
│ │ │
│ │ ├─refund
│ │ │ ├─1.0
│ │ │ │ api_1_0_refund.php
│ │ │ │
│ │ │ └─application_error
│ │ │ refund_application_error.php
│ │ │
│ │ ├─shipping
│ │ │ ├─1.0
│ │ │ │ api_b2b_1_0_area.php
│ │ │ │ api_b2b_1_0_corp.php
│ │ │ │ api_b2b_1_0_h_area.php
│ │ │ │ api_b2b_1_0_region.php
│ │ │ │ api_b2b_1_0_type.php
│ │ │ │
│ │ │ ├─2.0
│ │ │ │ api_b2b_2_0_region.php
│ │ │ │
│ │ │ └─application_error
│ │ │ b2b_region_application_error.php
│ │ │
│ │ ├─site
│ │ │ ├─1.0
│ │ │ │ api_1_0_site.php
│ │ │ │ api_b2b_1_0_brand.php
│ │ │ │ api_b2b_1_0_cat.php
│ │ │ │ api_b2b_1_0_cur.php
│ │ │ │ api_b2b_1_0_goodstype.php
│ │ │ │ api_b2b_1_0_spec.php
│ │ │ │
│ │ │ └─2.0
│ │ │ api_b2b_2_0_brand.php
│ │ │ api_b2b_2_0_cat.php
│ │ │ api_b2b_2_0_goodstype.php
│ │ │ api_b2b_2_0_spec.php
│ │ │ api_b2b_2_0_sync.php
│ │ │
│ │ ├─taobao
│ │ │ │ api_products.php
│ │ │ │
│ │ │ └─3.1
│ │ │ api_b2b_3_1_csvfile.php
│ │ │
│ │ └─tools
│ │ └─1.0
│ │ api_b2b_1_0_tools.php
│ │
│ ├─assistant
│ │ │ api.php
│ │ │
│ │ ├─lib
│ │ │ BaseService.php
│ │ │ BaseValidator.php
│ │ │ GeneralFunc.php
│ │ │ LogUtils.php
│ │ │ nudime.php
│ │ │ ServerUtils.php
│ │ │ TextUtils.php
│ │ │
│ │ ├─service
│ │ │ service.Login.php
│ │ │ service.PartView.php
│ │ │ service.ShopInfo.php
│ │ │ service.SyncFile.php
│ │ │ service.SyncRecord.php
│ │ │
│ │ └─validator
│ │ articles.1.validator.php
│ │ brand.1.validator.php
│ │ gimages.1.validator.php
│ │ goods.1.validator.php
│ │ goods_cat.1.validator.php
│ │ goods_keywords.1.validator.php
│ │ goods_lv_price.1.validator.php
│ │ goods_memo.1.validator.php
│ │ goods_rate.1.validator.php
│ │ goods_spec_index.1.validator.php
│ │ goods_type.1.validator.php
│ │ goods_type_spec.1.validator.php
│ │ member_lv.1.validator.php
│ │ products.1.validator.php
│ │ sitemaps.1.validator.php
│ │ specification.1.validator.php
│ │ spec_values.1.validator.php
│ │ tags.1.validator.php
│ │ tag_rel.1.validator.php
│ │ taobao_goods.1.validator.php
│ │ type_brand.1.validator.php
│ │
│ ├─entity
│ │ entity.goods.php
│ │ entity.member.php
│ │ entity.order.php
│ │
│ ├─html
│ │ ├─misc
│ │ │ orderprint.html
│ │ │
│ │ └─pages
│ │ about.html
│ │ business.html
│ │ contact.html
│ │ disclaimer.html
│ │ helpcenter.html
│ │ jobs.html
│ │ license.html
│ │ memberrank.html
│ │ method.html
│ │ newguide.html
│ │ nonmember.html
│ │ notice.html
│ │ onlinepayment.html
│ │ orderinfo.html
│ │ orderstatus.html
│ │ payment.html
│ │ privacy.html
│ │ process.html
│ │ returngood.html
│ │ scoreplan.html
│ │ service.html
│ │ shipping.html
│ │ shippinginfo.html
│ │ terms.html
│ │
│ ├─include 内容同 include_V5, 加密
│ │
│ ├─include_v5
│ │ │ adminCore.php
│ │ │ adminPage.php
│ │ │ adminSchema.php
│ │ │ AloneDB.php
│ │ │ app.php
│ │ │ cachemgr.php
│ │ │ crontab.php
│ │ │ ctlmap.php
│ │ │ dapi.php
│ │ │ datatypes.php
│ │ │ defined.php
│ │ │ delivercorp.php
│ │ │ hookFactory.php
│ │ │ http.php
│ │ │ magicvars_sys.php
│ │ │ mapfile.php
│ │ │ modelFactory.php
│ │ │ modifiers.php
│ │ │ objectPage.php
│ │ │ pageFactory.php
│ │ │ paymentPlugin.php
│ │ │ phpFtp.php
│ │ │ plugin.php
│ │ │ relation.php
│ │ │ secache.php
│ │ │ secache_no_flock.php
│ │ │ setmgr.php
│ │ │ setting.php
│ │ │ shopCore.php
│ │ │ shopctls.php
│ │ │ shopdav.php
│ │ │ shopObject.php
│ │ │ shopPage.php
│ │ │ shopPreview.php
│ │ │ shopSchema.php
│ │ │ simplehash.php
│ │ │
│ │ ├─core
│ │ │ action.export.php
│ │ │ action.finder_lister.php
│ │ │ db.split_sql.php
│ │ │ db.tools.php
│ │ │ gcat.get.php
│ │ │ goods.check_gspec.php
│ │ │ goods.check_import.php
│ │ │ goods.export.php
│ │ │ goods.filter.php
│ │ │ goods.filter_of_type.php
│ │ │ goods.get_filter.php
│ │ │ goods.get_spare_price.php
│ │ │ goods.import.php
│ │ │ goods.import_goodsline.php
│ │ │ goods.import_product.php
│ │ │ goods.import_productline.php
│ │ │ goods.insert.php
│ │ │ goods.insert_link.php
│ │ │ goods.list.php
│ │ │ goods.update_member_price.php
│ │ │ goods.update_price.php
│ │ │ object.batch_edit.php
│ │ │ object.cols.type.php
│ │ │ object.column_value.php
│ │ │ object.delete.php
│ │ │ object.export.php
│ │ │ object.filter_parser.php
│ │ │ object.insert.php
│ │ │ object.update.php
│ │ │
│ │ ├─funcs
│ │ │ get_class_struct.php
│ │ │ http.php
│ │ │
│ │ ├─shop
│ │ │ admin.menu_filter.php
│ │ │ core.debugger.php
│ │ │ core.location.php
│ │ │ core.match_network.php
│ │ │ core.time_auth.php
│ │ │ core.upgrade.php
│ │ │ shop.front_api.php
│ │ │
│ │ ├─smartyplugins
│ │ │ block.capture.php
│ │ │ block.safehtml.php
│ │ │ block.textformat.php
│ │ │ compiler.img.php
│ │ │ compiler.input.php
│ │ │ compiler.in_array.php
│ │ │ compiler.link.php
│ │ │ compiler.main.php
│ │ │ compiler.math.php
│ │ │ compiler.require.php
│ │ │ compiler.widgets.php
│ │ │ compile_modifier.cur.php
│ │ │ compile_modifier.default.php
│ │ │ compile_modifier.gimage.php
│ │ │ compile_modifier.safehtml.php
│ │ │ function.counter.php
│ │ │ function.cycle.php
│ │ │ function.fixurl.php
│ │ │ function.footer.php
│ │ │ function.goodsmenu.php
│ │ │ function.header.php
│ │ │ function.html_checkboxes.php
│ │ │ function.html_input.php
│ │ │ function.html_options.php
│ │ │ function.html_radios.php
│ │ │ function.html_select_date.php
│ │ │ function.html_select_time.php
│ │ │ function.html_table.php
│ │ │ function.javascript.php
│ │ │ function.link.php
│ │ │ function.mailto.php
│ │ │ function.pager.php
│ │ │ function.widgets.php
│ │ │ input.bool.php
│ │ │ input.checkbox.php
│ │ │ input.color.php
│ │ │ input.combox.php
│ │ │ input.date.php
│ │ │ input.default.php
│ │ │ input.fontset.php
│ │ │ input.gender.php
│ │ │ input.html.php
│ │ │ input.intbool.php
│ │ │ input.money.php
│ │ │ input.radio.php
│ │ │ input.region.php
│ │ │ input.select.php
│ │ │ input.textarea.php
│ │ │ input.time.php
│ │ │ input.tinybool.php
│ │ │ modifier.amount.php
│ │ │ modifier.cdate.php
│ │ │ modifier.cut.php
│ │ │ modifier.date.php
│ │ │ modifier.date_format.php
│ │ │ modifier.escape.php
│ │ │ modifier.gender.php
│ │ │ modifier.number.php
│ │ │ modifier.paddingleft.php
│ │ │ modifier.regex_replace.php
│ │ │ modifier.region.php
│ │ │ modifier.replace.php
│ │ │ modifier.shopbbsdate.php
│ │ │ modifier.storager.php
│ │ │ modifier.strip.php
│ │ │ modifier.styleset.php
│ │ │ modifier.timeleft.php
│ │ │ modifier.truncate.php
│ │ │ modifier.userdate.php
│ │ │ modifier.usertime.php
│ │ │ outputfilter.trimwhitespace.php
│ │ │ shared.escape_chars.php
│ │ │
│ │ └─template
│ │ compile.admin_plugin_path.php
│ │ compile.compile_custom_block.php
│ │ compile.compile_custom_function.php
│ │ compile.include.php
│ │ compile.plugin_path.php
│ │ compile.section_start.php
│ │
│ ├─lib
│ │ │ curl.php
│ │ │ json.php
│ │ │ mysqldumper.class.php
│ │ │ nusoap.php
│ │ │
│ │ ├─charset
│ │ │ char_local.php
│ │ │ char_utf.php
│ │ │ utf2zh.dat
│ │ │ zh2utf.dat
│ │ │
│ │ ├─pear
│ │ │ DIME.php
│ │ │ PEAR.php
│ │ │
│ │ └─uc_client
│ │ │ client.php
│ │ │ index.htm
│ │ │
│ │ ├─control
│ │ │ app.php
│ │ │ cache.php
│ │ │ domain.php
│ │ │ feed.php
│ │ │ friend.php
│ │ │ index.htm
│ │ │ pm.php
│ │ │ tag.php
│ │ │ user.php
│ │ │
│ │ ├─data
│ │ │ └─cache
│ │ ├─lib
│ │ │ db.class.php
│ │ │ index.htm
│ │ │ uccode.class.php
│ │ │ xml.class.php
│ │ │
│ │ └─model
│ │ app.php
│ │ base.php
│ │ cache.php
│ │ domain.php
│ │ friend.php
│ │ index.htm
│ │ misc.php
│ │ note.php
│ │ pm.php
│ │ tag.php
│ │ user.php
│ │
│ ├─model 内容同model_v5,加密
│ │
│ ├─model_v5
│ │ │ mdl.adminprofile.php
│ │ │
│ │ ├─admin
│ │ │ mdl.adminroles.php
│ │ │ mdl.operator.php
│ │ │
│ │ ├─comment
│ │ │ mdl.comment.php
│ │ │ mdl.discuss.php
│ │ │ mdl.gask.php
│ │ │
│ │ ├─content
│ │ │ mdl.article.php
│ │ │ mdl.compile_widgets.php
│ │ │ mdl.custommessage.php
│ │ │ mdl.frendlink.php
│ │ │ mdl.page.php
│ │ │ mdl.sitemap.php
│ │ │ mdl.systmpl.php
│ │ │ mdl.widgets.php
│ │ │
│ │ ├─distribution
│ │ │ mdl.autosync.php
│ │ │ mdl.costsync.php
│ │ │ mdl.datasync.php
│ │ │ mdl.supplier.php
│ │ │ mdl.syncjob.php
│ │ │
│ │ ├─goods
│ │ │ mdl.brand.php
│ │ │ mdl.finderPdt.php
│ │ │ mdl.gimage.php
│ │ │ mdl.goodsNotify.php
│ │ │ mdl.gtype.php
│ │ │ mdl.gtypetransform.php
│ │ │ mdl.productCat.php
│ │ │ mdl.products.php
│ │ │ mdl.schema.php
│ │ │ mdl.search.php
│ │ │ mdl.specification.php
│ │ │ mdl.virtualcat.php
│ │ │
│ │ ├─member
│ │ │ mdl.account.php
│ │ │ mdl.advance.php
│ │ │ mdl.level.php
│ │ │ mdl.member.php
│ │ │ mdl.memberattr.php
│ │ │ mdl.passport.php
│ │ │
│ │ ├─purchase
│ │ │ mdl.b2bcur.php
│ │ │ mdl.order_po.php
│ │ │ mdl.po.php
│ │ │
│ │ ├─resources
│ │ │ mdl.message.php
│ │ │ mdl.msgbox.php
│ │ │ mdl.shopbbs.php
│ │ │ mdl.tmpimage.php
│ │ │
│ │ ├─service
│ │ │ mdl.apiclient.php
│ │ │ mdl.app_center.php
│ │ │ mdl.certificate.php
│ │ │ mdl.data_install.php
│ │ │ mdl.kft.php
│ │ │ mdl.saas.php
│ │ │ mdl.saasdata.php
│ │ │
│ │ ├─sync
│ │ │ └─order
│ │ │ mdl.order.php
│ │ │
│ │ ├─system
│ │ │ mdl.addons.php
│ │ │ mdl.appmgr.php
│ │ │ mdl.backup.php
│ │ │ mdl.cur.php
│ │ │ mdl.dataio.php
│ │ │ mdl.debug.php
│ │ │ mdl.email.php
│ │ │ mdl.entity.php
│ │ │ mdl.frontend.php
│ │ │ mdl.local.php
│ │ │ mdl.magicvars.php
│ │ │ mdl.math.php
│ │ │ mdl.messenger.php
│ │ │ mdl.messenger0.php
│ │ │ mdl.pubfile.php
│ │ │ mdl.seo.php
│ │ │ mdl.sfile.php
│ │ │ mdl.status.php
│ │ │ mdl.storager.php
│ │ │ mdl.tag.php
│ │ │ mdl.template.php
│ │ │ mdl.tramsy.php
│ │ │ mdl.trigger.php
│ │ │ mdl.upgrade.php
│ │ │
│ │ ├─trading
│ │ │ mdl.cart.php
│ │ │ mdl.coupon.php
│ │ │ mdl.couponGenerate.php
│ │ │ mdl.delivery.php
│ │ │ mdl.deliveryarea.php
│ │ │ mdl.deliverycorp.php
│ │ │ mdl.dly_centers.php
│ │ │ mdl.dly_printer.php
│ │ │ mdl.exchangeCoupon.php
│ │ │ mdl.gift.php
│ │ │ mdl.giftcat.php
│ │ │ mdl.goods.php
│ │ │ mdl.memberExperience.php
│ │ │ mdl.memberPoint.php
│ │ │ mdl.order.php
│ │ │ mdl.package.php
│ │ │ mdl.payment.php
│ │ │ mdl.paymentcfg.php
│ │ │ mdl.point.php
│ │ │ mdl.pointHistory.php
│ │ │ mdl.promotion.php
│ │ │ mdl.promotionActivity.php
│ │ │ mdl.promotionScheme.php
│ │ │ mdl.refund.php
│ │ │ mdl.reship.php
│ │ │ mdl.return_product.php
│ │ │ mdl.sale.php
│ │ │ mdl.shipping.php
│ │ │ mdl.taobaoordercsv.php
│ │ │
│ │ └─utility
│ │ mdl.archive.php
│ │ mdl.barcode.php
│ │ mdl.charset.php
│ │ mdl.data_verify.php
│ │ mdl.gdimage.php
│ │ mdl.http_client.php
│ │ mdl.language.php
│ │ mdl.magickwand.php
│ │ mdl.rest_client.php
│ │ mdl.salescount.php
│ │ mdl.schemas.php
│ │ mdl.serverinfo.php
│ │ mdl.sha1.php
│ │ mdl.smtp.php
│ │ mdl.tar.php
│ │ mdl.tools.php
│ │ mdl.url.php
│ │ mdl.vcode.php
│ │ mdl.verifyCode.php
│ │ mdl.xml.php
│ │
│ ├─schemas
│ │ admin_roles.php
│ │ advance_logs.php
│ │ articles.php
│ │ autosync_rule.php
│ │ autosync_rule_relation.php
│ │ autosync_task.php
│ │ brand.php
│ │ cachemgr.php
│ │ comments.php
│ │ cost_sync.php
│ │ coupons.php
│ │ coupons_p_items.php
│ │ coupons_u_items.php
│ │ ctlmap.php
│ │ currency.php
│ │ dapi.php
│ │ delivery.php
│ │ delivery_item.php
│ │ dly_area.php
│ │ dly_center.php
│ │ dly_corp.php
│ │ dly_h_area.php
│ │ dly_type.php
│ │ gift.php
│ │ gift_cat.php
│ │ gift_items.php
│ │ gimages.php
│ │ gnotify.php
│ │ goods.php
│ │ goods_cat.php
│ │ goods_keywords.php
│ │ goods_lv_price.php
│ │ goods_memo.php
│ │ goods_rate.php
│ │ goods_spec_index.php
│ │ goods_type.php
│ │ goods_type_spec.php
│ │ goods_virtual_cat.php
│ │ gtask.php
│ │ image_sync.php
│ │ job_apilist.php
│ │ job_data_sync.php
│ │ job_goods_download.php
│ │ link.php
│ │ lnk_acts.php
│ │ lnk_roles.php
│ │ magicvars.php
│ │ members.php
│ │ member_addrs.php
│ │ member_attr.php
│ │ member_coupon.php
│ │ member_lv.php
│ │ member_mattrvalue.php
│ │ message.php
│ │ msgqueue.php
│ │ operators.php
│ │ op_sessions.php
│ │ orders.php
│ │ order_items.php
│ │ order_log.php
│ │ order_pmt.php
│ │ package_product.php
│ │ pages.php
│ │ payments.php
│ │ payment_cfg.php
│ │ plugins.php
│ │ pmt_gen_coupon.php
│ │ pmt_goods.php
│ │ pmt_goods_cat.php
│ │ pmt_member_lv.php
│ │ point_history.php
│ │ print_tmpl.php
│ │ products.php
│ │ product_memo.php
│ │ promotion.php
│ │ promotion_activity.php
│ │ promotion_scheme.php
│ │ pub_files.php
│ │ refunds.php
│ │ regions.php
│ │ return_product.php
│ │ sell_logs.php
│ │ sendbox.php
│ │ seo.php
│ │ settings.php
│ │ sfiles.php
│ │ sitemaps.php
│ │ specification.php
│ │ spec_values.php
│ │ status.php
│ │ supplier.php
│ │ supplier_pdtbn.php
│ │ sync_tmp.php
│ │ systmpl.php
│ │ tags.php
│ │ tag_rel.php
│ │ template_relation.php
│ │ themes.php
│ │ tpl_source.php
│ │ triggers.php
│ │ trust_login.php
│ │ type_brand.php
│ │ widgets_set.php
│ │
│ ├─shop
│ │ ├─controller
│ │ │ ctl.article.php
│ │ │ ctl.artlist.php
│ │ │ ctl.brand.php
│ │ │ ctl.cart.php
│ │ │ ctl.comment.php
│ │ │ ctl.custompage.php
│ │ │ ctl.gallery.php
│ │ │ ctl.gift.php
│ │ │ ctl.link.php
│ │ │ ctl.member.php
│ │ │ ctl.message.php
│ │ │ ctl.order.php
│ │ │ ctl.package.php
│ │ │ ctl.page.php
│ │ │ ctl.passport.php
│ │ │ ctl.paycenter.php
│ │ │ ctl.previewtheme.php
│ │ │ ctl.product.php
│ │ │ ctl.search.php
│ │ │ ctl.sitemaps.php
│ │ │ ctl.tools.php
│ │ │ ctl.wholesale.php
│ │ │
│ │ └─view
│ │ ├─article
│ │ │ index.html
│ │ │
│ │ ├─artlist
│ │ │ index.html
│ │ │
│ │ ├─brand
│ │ │ index.html
│ │ │ showList.html
│ │ │
│ │ ├─cart
│ │ │ cart_total.html
│ │ │ checkout.html
│ │ │ checkout_base.html
│ │ │ checkout_shipping.html
│ │ │ checkout_total.html
│ │ │ index.html
│ │ │ loginBuy.html
│ │ │ loginbuy_fast.html
│ │ │ mini_cart.html
│ │ │ mini_cart_list.html
│ │ │ view.html
│ │ │
│ │ ├─comment
│ │ │ commentlist.html
│ │ │ reply.html
│ │ │
│ │ ├─common
│ │ │ delivery.html
│ │ │ dialog_receiver.html
│ │ │ footer.html
│ │ │ header.html
│ │ │ orderinfo.html
│ │ │ paymethod.html
│ │ │ receiver.html
│ │ │ rec_addr.html
│ │ │ upgrade.html
│ │ │
│ │ ├─gallery
│ │ │ │ index.html
│ │ │ │
│ │ │ ├─selector
│ │ │ │ default.html
│ │ │ │ multi.html
│ │ │ │
│ │ │ └─type
│ │ │ grid.html
│ │ │ list.html
│ │ │ text.html
│ │ │
│ │ ├─gift
│ │ │ index.html
│ │ │ showList.html
│ │ │
│ │ ├─link
│ │ │ showList.html
│ │ │
│ │ ├─member
│ │ │ addOrderMsg.html
│ │ │ addReceiver.html
│ │ │ balance.html
│ │ │ comment.html
│ │ │ coupon.html
│ │ │ couponExchange.html
│ │ │ deposit.html
│ │ │ favorite.html
│ │ │ inbox.html
│ │ │ index.html
│ │ │ main.html
│ │ │ message.html
│ │ │ modifyReceiver.html
│ │ │ notify.html
│ │ │ orderdetail.html
│ │ │ orderpay.html
│ │ │ orders.html
│ │ │ outbox.html
│ │ │ pointHistory.html
│ │ │ receiver.html
│ │ │ return_add.html
│ │ │ return_details.html
│ │ │ return_list.html
│ │ │ return_list_item.html
│ │ │ return_order_list.html
│ │ │ return_policy.html
│ │ │ security.html
│ │ │ send.html
│ │ │ setting.html
│ │ │ track.html
│ │ │
│ │ ├─message
│ │ │ index.html
│ │ │
│ │ ├─order
│ │ │ detail.html
│ │ │ index.html
│ │ │
│ │ ├─package
│ │ │ index.html
│ │ │
│ │ ├─page
│ │ │ error.html
│ │ │ single-page.html
│ │ │ system-error.html
│ │ │
│ │ ├─passport
│ │ │ │ create.html
│ │ │ │ error.html
│ │ │ │ index.html
│ │ │ │ login.html
│ │ │ │ lost.html
│ │ │ │ other_login.html
│ │ │ │ recover.html
│ │ │ │ signup.html
│ │ │ │
│ │ │ └─index
│ │ │ login.html
│ │ │ login_fast.html
│ │ │ recover.html
│ │ │ signup.html
│ │ │ signup_fast.html
│ │ │
│ │ ├─paycenter
│ │ │ order.html
│ │ │ result.html
│ │ │
│ │ ├─product
│ │ │ diff.html
│ │ │ gnotify.html
│ │ │ goodspics.html
│ │ │ index.html
│ │ │ menu.html
│ │ │ selllog.html
│ │ │ viewpic.html
│ │ │
│ │ ├─search
│ │ │ index.html
│ │ │ showCat.html
│ │ │
│ │ ├─sitemaps
│ │ │ view.html
│ │ │
│ │ ├─splash
│ │ │ failed.html
│ │ │ success.html
│ │ │
│ │ ├─tools
│ │ │ history.html
│ │ │
│ │ └─utility
│ │ select.html
│ │
│ └─updatescripts
│ │ 11705.sql
│ │ 12175.sql
│ │ 12659.sql
│ │ 13035.sql
│ │ 13936.sql
│ │ 15211.sql
│ │ 16111.sql
│ │ 16587.php
│ │ 16587.sql
│ │ 18137.php
│ │ 18137.sql
│ │ 21246.php
│ │ 21246.sql
│ │ 27449.sql
│ │ 28008.php
│ │ 28009.sql
│ │ 31323.sql
│ │ 32890.sql
│ │ 35866.php
│ │ 35866.sql
│ │ 40195.php
│ │ 50000.php
│ │
│ └─21246
│ area.txt

├─home
│ ├─backup 备份数据
│ ├─cache 缓存数据
│ │ │
│ │ │ cachedata.php 前台全页缓存
│ │ ├─admin_tmpl 后台模板编译
│ │ └─front_tmpl 前台模板编译
│ │
│ ├─download 下载
│ ├─logs 日志
│ ├─sendtmp
│ │ defaultApp.log
│ │ hytmp.php
│ │
│ ├─tmp
│ └─upload 上传的源文件

├─images 媒体文件目录
│ ├─20120202 文章图片, 商品描述,商品图片,模板中上传的图片,按月存放
│ ├─article ...
│ ├─default 默认规格,上传的logo,上传的系统用到的非广告文件,如系统默认图片
│ │ │ default_big_pic.gif
│ │ │ ......
│ │ │ wm_small_pic.png
│ │ │
│ │ └─upload
│ ├─gift 积分商品
│ └─goods 商品的缩略图
├─install 安装目录
│ │ index.php
│ │ info.xml
│ │ install.core.php
│ │ svinfo.php 探针
│ │
│ ├─dbscripts
│ │ demo.sql
│ │ init.sql
│ │
│ ├─images
│ │
│ ├─templates_c
│ │
│ └─view

├─plugins
│ │ loader.php
│ │
│ ├─actions 网店机器人
│ │ action.member.php
│ │ action.order.php
│ │ action.system.php
│ │
│ ├─app 应用中心
│ │ ├─commodity_radar 商品雷达
│ │ │ │ app.commodity_radar.php
│ │ │ │ commodity_radar_default_modifiers.php
│ │ │ │ commodity_radar_public.php
│ │ │ │
│ │ │ └─view
│ │ │ finder_name.html
│ │ │
│ │ ├─openid_taobao 淘宝信任登录
│ │ │ .app.openid_taobao.php.swo
│ │ │ .app.openid_taobao.php.swp
│ │ │ app.openid_taobao.php
│ │ │ app.openid_taobao.php.bak
│ │ │ mdl.openid_taobao_center_send.php
│ │ │
│ │ ├─pay_alipay
│ │ │ app.pay_alipay.php
│ │ │ mdl.center_send.php
│ │ │ paymentPlugin.php
│ │ │ pay_alipay.php
│ │ │ pay_alipay.server.php
│ │ │
│ │ ├─pay_deposit
│ │ │ app.pay_deposit.php
│ │ │ paymentPlugin.php
│ │ │ pay_deposit.php
│ │ │
│ │ ├─pay_haipay
│ │ │ app.pay_haipay.php
│ │ │ paymentPlugin.php
│ │ │ pay_haipay.php
│ │ │
│ │ ├─pay_offline
│ │ │ app.pay_offline.php
│ │ │ paymentPlugin.php
│ │ │ pay_offline.php
│ │ │
│ │ ├─pay_tenpaytrad
│ │ │ app.pay_tenpaytrad.php
│ │ │ mdl.center_send.php
│ │ │ paymentPlugin.php
│ │ │ pay_tenpaytrad.php
│ │ │ pay_tenpaytrad.server.php
│ │ │
│ │ ├─shopex_stat 营销统计工具
│ │ │ │ admin.stat_ctl.php
│ │ │ │ app.shopex_stat.php
│ │ │ │ mdl.shopex_stat.php
│ │ │ │ shopex_stat_lismember.php
│ │ │ │ shopex_stat_lisproduct.php
│ │ │ │ shopex_stat_listener.php
│ │ │ │ shopex_stat_modifiers.php
│ │ │ │
│ │ │ └─view
│ │ │ index.html
│ │ │
│ │ ├─taobao_goods 同步管理淘宝商品
│ │ │ │ admin.ctl_taobao_goods.php
│ │ │ │ app.taobao_goods.php
│ │ │ │ icon.png
│ │ │ │ mdl.center_send.php
│ │ │ │ mdl.gtypetransform.php
│ │ │ │ mdl.taobao.php
│ │ │ │ setting.php
│ │ │ │ taobao_goods_product_listener.php
│ │ │ │ taobao_goods_product_modifiers.php
│ │ │ │
│ │ │ ├─dbschema
│ │ │ │ goods.php
│ │ │ │
│ │ │ ├─images
│ │ │ │
│ │ │ └─view
│ │ │ debug.html
│ │ │ goods_props.html
│ │ │ product_add.html
│ │ │ select_category.html
│ │ │ sess_timeout.html
│ │ │ spec_row.html
│ │ │ taobao_cat.html
│ │ │ taobao_postage.html
│ │ │ tb_areas.html
│ │ │ tb_cats.html
│ │ │
│ │ ├─tb_order_ctl 同步处理淘宝订单
│ │ │ │ admin.order_ctl.php
│ │ │ │ admin.tb_notify.php
│ │ │ │ app.tb_order_ctl.php
│ │ │ │ icon.png
│ │ │ │ mdl.center_send.php
│ │ │ │ mdl.tborder.php
│ │ │ │ setting.php
│ │ │ │ tb_notify.php
│ │ │ │
│ │ │ ├─dbdata
│ │ │ │ taobao_region.sql
│ │ │ │
│ │ │ ├─dbschema
│ │ │ │ orders.php
│ │ │ │ order_items.php
│ │ │ │ regions.php
│ │ │ │
│ │ │ ├─images
│ │ │ │
│ │ │ └─view
│ │ │ │ sess_timeout.html
│ │ │ │ set_nick.html
│ │ │ │
│ │ │ └─order
│ │ │ addtradenote.html
│ │ │ finder_action.html
│ │ │ orderconsign.html
│ │ │ orderprint.html
│ │ │ order_detail.html
│ │ │ order_status.html
│ │ │ set_order_price.html
│ │ │ taobao_frame.html
│ │ │
│ │ └─tb_sales_download 下载淘宝销售记录
│ │ │ admin.sales_ctl.php
│ │ │ app.tb_sales_download.php
│ │ │ icon.png
│ │ │ mdl.center_send.php
│ │ │ mdl.tbsales.php
│ │ │ setting.php
│ │ │
│ │ ├─dbschema
│ │ │ sell_log.php
│ │ │
│ │ ├─images
│ │ │
│ │ └─view
│ │ │ sess_timeout.html
│ │ │ set_nick.html
│ │ │
│ │ └─sales
│ │ dotaobao_rate.html
│ │
│ ├─dataio 数据导入导出插件
│ │ io.csv.php
│ │ io.gtype.php
│ │ io.sitezol.php
│ │ io.taobaogoodscsv.php
│ │ io.taobaoordercsv.php
│ │ io.txt.php
│ │ io.xls.php
│ │ io.xml.php
│ │
│ ├─functions
│ │ cacheApc.php
│ │ cachedir.php
│ │ cache_memcache.php
│ │ fs_storage.php
│ │ ftp_storage.php
│ │ tt_storage.php
│ │ urlmap.php
│ │
│ ├─layout 单独页面布局插件
│ │ ├─1-column
│ │ │ layout.html
│ │ │ layout_1-column.php
│ │ │ preview.png
│ │ │
│ │ ├─2-columns-left
│ │ │ layout.html
│ │ │ layout_2-columns-left.php
│ │ │ preview.png
│ │ │
│ │ ├─2-columns-right
│ │ │ layout.html
│ │ │ layout_2-columns-right.php
│ │ │ preview.png
│ │ │
│ │ └─3-columns
│ │ layout.html
│ │ layout_3-columns.php
│ │ preview.png
│ │
│ ├─location 地区数据插件
│ │ └─mainland
│ │ area.txt
│ │ local.mainland.php
│ │ regions.csv
│ │
│ ├─messenger 用户消息插件
│ │ ├─email
│ │ │ account-chgpass.html
│ │ │ account-lostPw.html
│ │ │ account-register.html
│ │ │ goods-notify.html
│ │ │ messenger.email.php
│ │ │ order-cancel.html
│ │ │ order-create.html
│ │ │ order-payed.html
│ │ │ order-refund.html
│ │ │ order-returned.html
│ │ │ order-shipping.html
│ │ │
│ │ ├─msgbox
│ │ │ account-chgpass.html
│ │ │ account-lostPw.html
│ │ │ account-register.html
│ │ │ goods-notify.html
│ │ │ messenger.msgbox.php
│ │ │ order-cancel.html
│ │ │ order-create.html
│ │ │ order-payed.html
│ │ │ order-refund.html
│ │ │ order-returned.html
│ │ │ order-shipping.html
│ │ │
│ │ └─sms
│ │ account-chgpass.html
│ │ account-lostPw.html
│ │ account-register.html
│ │ goods-notify.html
│ │ messenger.sms.php
│ │ order-cancel.html
│ │ order-create.html
│ │ order-payed.html
│ │ order-refund.html
│ │ order-returned.html
│ │ order-shipping.html
│ │
│ ├─passport 用户登录插件
│ │ passport.discuz.php
│ │ passport.phpwind.php
│ │ passport.phpwind7.php
│ │ passport.ucenter.php
│ │
│ ├─payment 支付方式插件
│ │ │ disabled_payments.txt
│ │ │ pay.2checkout.php
│ │ │ pay.6688.php
│ │ │ pay.800pay.php
│ │ │ pay.99bill.php
│ │ │ pay.abank.php
│ │ │ pay.alipay.php
│ │ │ pay.alipay.server.php
│ │ │ pay.alipaytrad.php
│ │ │ pay.alipaytrad.server.php
│ │ │ pay.chinapay.php
│ │ │ pay.chinapnr.php
│ │ │ pay.chinapnr.server.php
│ │ │ pay.cmbc.php
│ │ │ pay.cncard.php
│ │ │ pay.cncard.server.php
│ │ │ pay.deposit.php
│ │ │ pay.ecs.php
│ │ │ pay.egold.php
│ │ │ pay.enets.php
│ │ │ pay.epay.php
│ │ │ pay.google.php
│ │ │ pay.gwpay.php
│ │ │ pay.haipay.php
│ │ │ pay.haipay.server.php
│ │ │ pay.homeway.php
│ │ │ pay.hyl.php
│ │ │ pay.icbc.php
│ │ │ pay.iepay.php
│ │ │ pay.ipay.php
│ │ │ pay.ips3.php
│ │ │ pay.ips3.server.php
│ │ │ pay.mobile88.php
│ │ │ pay.moneybookers.php
│ │ │ pay.nochek.php
│ │ │ pay.npay.php
│ │ │ pay.nps.php
│ │ │ pay.nps_out.php
│ │ │ pay.offline.php
│ │ │ pay.pay100.php
│ │ │ pay.paydollar.php
│ │ │ pay.paypal.php
│ │ │ pay.paypal.server.php
│ │ │ pay.paypal_cn.php
│ │ │ pay.shouxin.php
│ │ │ pay.skypay.php
│ │ │ pay.smilepay.php
│ │ │ pay.tenpay.php
│ │ │ pay.tenpaytrad.php
│ │ │ pay.tenpaytrad.server.php
│ │ │ pay.twv.php
│ │ │ pay.udpay.php
│ │ │ pay.udpay.server.php
│ │ │ pay.unspay.php
│ │ │ pay.wangjin_out.php
│ │ │ pay.wangyin.php
│ │ │ pay.yeepay.php
│ │ │ paymentPlugin.php
│ │ │
│ │ ├─images
│ │ │
│ │ └─reg
│ │ pay.99billreg.php
│ │
│ ├─pmtScheme 促销方式插件
│ │ pmt.c_1.php
│ │ pmt.c_2.php
│ │ pmt.c_3.php
│ │ pmt.c_4.php
│ │ pmt.c_5.php
│ │ pmt.c_6.php
│ │ pmt.c_7.php
│ │ pmt.n_1.php
│ │ pmt.n_2.php
│ │ pmt.n_3.php
│ │ pmt.n_4.php
│ │ pmt.n_5.php
│ │ pmt.n_6.php
│ │ pmt.n_7.php
│ │
│ ├─schema 商品插件
│ │ ├─custom
│ │ │ │ icon-48x48.png
│ │ │ │ schema.custom.php
│ │ │ │
│ │ │ └─view
│ │ │ init.html
│ │ │
│ │ └─simple
│ │ icon-48x48.png
│ │ schema.simple.php
│ │
│ └─widgets 网页挂件
│ ├─ad
│ │ default.html
│ │ widgets.php
│ │ widget_ad.php
│ │ _config.html
│ │
│ ├─ad_curtain
│ │ │ default.html
│ │ │ widgets.php
│ │ │ widget_ad_curtain.php
│ │ │ _config.html
│ │ │
│ │ └─swf
│ │ main.swf
│ │
│ ├─ad_flash
│ │ default.html
│ │ widgets.php
│ │ _config.html
│ │
│ ├─ad_multiple
│ │ │ default.html
│ │ │ widgets.php
│ │ │ widget_ad_multiple.php
│ │ │ _config.html
│ │ │
│ │ ├─images
│ │ │
│ │ └─swf
│ │ main.swf
│ │
│ ├─ad_pic
│ │ default.html
│ │ widgets.php
│ │ widget_ad_pic.php
│ │ _config.html
│ │
│ ├─ad_text
│ │ default.html
│ │ widgets.php
│ │ widget_cfg_ad_text.php
│ │ _config.html
│ │
│ ├─ad_userdefine
│ │ default.html
│ │ widgets.php
│ │ _config.html
│ │ _preview.html
│ │
│ ├─article
│ │ default.html
│ │ div_list.html
│ │ multi_list.html
│ │ widgets.php
│ │ widget_article.php
│ │ widget_cfg_article.php
│ │ _config.html
│ │
│ ├─brand
│ │ default.html
│ │ newBrandList.html
│ │ widgets.php
│ │ widget_brand.php
│ │ widget_cfg_brand.php
│ │ _config.html
│ │
│ ├─cart
│ │ default.html
│ │ widgets.php
│ │ _preview.html
│ │
│ ├─circle
│ │ │ default.html
│ │ │ widgets.php
│ │ │ widget_circle.php
│ │ │ _config.html
│ │ │ _preview.html
│ │ │
│ │ └─images
│ │ style1.swf
│ │
│ ├─comment
│ │ default.html
│ │ widgets.php
│ │ widget_comment.php
│ │ _config.html
│ │
│ ├─coverflow
│ │ │ default.html
│ │ │ widgets.php
│ │ │ widget_coverflow.php
│ │ │ _config.html
│ │ │ _preview.html
│ │ │
│ │ └─images
│ │ style1.swf
│ │ test.swf
│ │
│ ├─exchangeeffect
│ │ default.html
│ │ widgets.php
│ │ widget_exchangeeffect.php
│ │ _config.html
│ │ _preview.html
│ │
│ ├─flashview
│ │ │ default.html
│ │ │ widgets.php
│ │ │ widget_flashview.php
│ │ │ _config.html
│ │ │ _preview.html
│ │ │
│ │ └─images
│ │ 1.swf
│ │
│ ├─floweffect
│ │ │ default.html
│ │ │ widgets.php
│ │ │ widget_floweffect.php
│ │ │ _config.html
│ │ │ _preview.html
│ │ │
│ │ └─images
│ │ style1.swf
│ │
│ ├─gift
│ │ default.html
│ │ widgets.php
│ │ widget_gift.php
│ │ _config.html
│ │
│ ├─gifttree
│ │ default.html
│ │ widgets.php
│ │ widget_gifttree.php
│ │ _config.html
│ │
│ ├─goods
│ │ default.html
│ │ list2.html
│ │ morediv.html
│ │ pic-morediv.html
│ │ widgets.php
│ │ widget_cfg_goods.php
│ │ widget_goods.php
│ │ _config.html
│ │
│ ├─goodscat
│ │ default.html
│ │ dropdown.html
│ │ toggle.html
│ │ widgets.php
│ │ widget_goodscat.php
│ │ _config.html
│ │
│ ├─goods_show
│ │ default.html
│ │ widgets.php
│ │ widget_cfg_goods_show.php
│ │ widget_goods_show.php
│ │ _config.html
│ │
│ ├─hst
│ │ default.html
│ │ widgets.php
│ │ widget_hst.php
│ │ _config.html
│ │ _preview.html
│ │
│ ├─im
│ │ │ default.html
│ │ │ siderIm.html
│ │ │ style1.html
│ │ │ style2.html
│ │ │ style3.html
│ │ │ widgets.php
│ │ │ widget_im.php
│ │ │ _config.html
│ │ │ _preview.html
│ │ │
│ │ └─images
│ │ siderIM_bg.gif
│ │ siderIM_bottom.gif
│ │ siderIM_hiddenBar.gif
│ │ siderIM_infobox.gif
│ │ siderIM_title.gif
│ │
│ ├─include
│ │ default.html
│ │ widgets.php
│ │ _config.html
│ │
│ ├─kf
│ │ default.html
│ │ widgets.php
│ │ widget_kf.php
│ │ _config.html
│ │ _preview.html
│ │
│ ├─links
│ │ default.html
│ │ linksDiv.html
│ │ style2.html
│ │ widgets.php
│ │ widget_links.php
│ │ _config.html
│ │
│ ├─logo
│ │ default.html
│ │ widgets.php
│ │
│ ├─member
│ │ bar.html
│ │ default.html
│ │ widgets.php
│ │ widget_member.php
│ │
│ ├─menutree
│ │ default.html
│ │ widgets.php
│ │ widget_menutree.php
│ │ _config.html
│ │
│ ├─menu_lv1
│ │ default.html
│ │ widgets.php
│ │ widget_menu_lv1.php
│ │ _config.html
│ │
│ ├─nav
│ │ default.html
│ │ widgets.php
│ │ widget_nav.php
│ │
│ ├─orderlist
│ │ default.html
│ │ widgets.php
│ │ widget_orderlist.php
│ │ _config.html
│ │
│ ├─rankinglist
│ │ default.html
│ │ widgets.php
│ │ widget_rankinglist.php
│ │ _config.html
│ │
│ ├─search
│ │ default.html
│ │ vertical.html
│ │ widgets.php
│ │ widget_search.php
│ │ _config.html
│ │
│ ├─slideEffectBanner
│ │ default.html
│ │ widgets.php
│ │ _config.html
│ │ _preview.html
│ │
│ ├─topbar
│ │ │ default.html
│ │ │ widgets.php
│ │ │ widget_topbar.php
│ │ │ _config.html
│ │ │
│ │ └─lang
│ │ en.php
│ │
│ ├─transport
│ │ default.html
│ │ widgets.php
│ │ widget_transport.php
│ │ _config.html
│ │
│ ├─treelist
│ │ default.html
│ │ widgets.php
│ │ widget_cfg_treelist.php
│ │ widget_treelist.php
│ │ _config.html
│ │
│ ├─usercustom
│ │ default.html
│ │ widgets.php
│ │ _config.html
│ │ _preview.html
│ │
│ ├─video
│ │ │ default.html
│ │ │ widgets.php
│ │ │ widget_video.php
│ │ │ _config.html
│ │ │ _preview.html
│ │ │
│ │ └─images
│ │ style1.swf
│ │
│ └─virtualcat
│ default.html
│ dropdown.html
│ multree.html
│ toggle.html
│ treemenu.js
│ widgets.php
│ widget_cfg_virtualcat.php
│ widget_virtualcat.php
│ _config.html

├─public
│ │ readme.txt
│ │
│ └─fonts
├─shopadmin 后台管理
│ │ 401.html 401
│ │ api.php
│ │ blank.html
│ │ google_gears.html
│ │ index.php
│ │ mobile.php
│ │ shopadmin-manifest.php
│ │ shortcut_key_help.html 快捷键帮助
│ │ wysiwyg_editor.css
│ │
│ ├─css 后台样式
│ │ adminsinglepage.css
│ │ adminsinglepage.zcss
│ │ button.css
│ │ component.css
│ │ finder.css
│ │ forms.css
│ │ grid.css
│ │ gridlist.css
│ │ ie.css
│ │ page.css
│ │ print.css
│ │ product.css
│ │ purchase.css
│ │ reset.css
│ │ shopadmin.css
│ │ shopadmin.zcss
│ │ singlepage.css
│ │ style.css
│ │ template.css
│ │ typography.css
│ │
│ ├─demo_data 模拟数据
│ │ install_industry_data.php
│ │
│ ├─images 后台用到的图片文件,背景,图片插入
│ │
│ ├─js js文件
│ │ │ fixie6.js
│ │ │ jstools.js
│ │ │ moo.js
│ │ │ mooadapter.js
│ │ │ moomore.js
│ │ │ sync_b2b.js
│ │ │
│ │ ├─coms
│ │ │ ajaks.js
│ │ │ areaselect.js
│ │ │ colorpicker.js
│ │ │ datapicker.js
│ │ │ dialog.js
│ │ │ dragdropplus.js
│ │ │ dropmenu.js
│ │ │ editor.js
│ │ │ editor_style_1.js
│ │ │ finder.js
│ │ │ goodseditor.js
│ │ │ historymanager.js
│ │ │ init.js
│ │ │ messagebox.js
│ │ │ productedit.js
│ │ │ shopwidgets.js
│ │ │ splash.js
│ │ │ taginputer.js
│ │ │ tagopt.js
│ │ │ treelist.js
│ │ │ uploader.js
│ │ │ validator.js
│ │ │
│ │ └─package
│ │ admin.jgz
│ │ admin.js
│ │ component.jgz
│ │ component.js
│ │ goodsedit.jgz
│ │ goodsedit.js
│ │ tools.jgz
│ │ tools.js
│ │ widgetsedit.jgz
│ │ widgetsedit.js
│ │ wysiwyg.jgz
│ │ wysiwyg.js
│ │
│ └─user_guide 用户向导
│ │ download_progress.html
│ │ install_industry_data.html
│ │ step1.html
│ │ ...
│ │ step7.html
│ │
│ └─images

├─statics 前台用到的js文件和图像文件
│ │ allowdrop.gif
│ │ arrowdown.gif
│ │ ......
│ ├─accountlogos 信任登陆
│ │
│ ├─bank 银行图标
│ │
│ ├─code 验证码图片
│ │
│ ├─icons 图标文件
│ │
│ ├─im 滚动聊天窗口
│ │
│ ├─remark_icons 标记
│ │
│ ├─script js
│ │ formplus.js
│ │ goodscupcake.js
│ │ tools.jgz
│ │ tools.js
│ │
│ └─script_src js源文件
│ broswerstore.js
│ formplus.js
│ goodscupcake.js
│ jstools.js
│ mootools.js

└─themes 主题目录
└─purple 系统自带主题
│ cart.html
│ default-pro.html
│ default.html
│ gift.html
│ index.html
│ info.xml
│ member.html
│ order_detail.html
│ order_index.html
│ page-pro2.html
│ page.html
│ passport.html
│ paycenter.bak_1.html
│ paycenter.bak_2.html
│ paycenter.html
│ preview.jpg
│ theme-bak.xml
│ theme.xml

├─block
│ footer.html
│ header.html

├─borders
│ 1pxGrayBorder.html
│ border4.html
│ border5.html
│ border_b.html
│ border_conergray.html
│ border_darkblue.html
│ border_gray.html
│ border_lightblue.html
│ border_lightgray.html
│ border_lightgreen.html
│ border_lightred.html
│ border_orange.html
│ border_y.html
│ siderCommonBorder.html
│ siderMainBorder_blue.html
│ siderMainBorder_green.html
│ siderMainBorder_red.html
│ sider_ad.html
│ sider_cat.html
│ sider_tejia.html

└─images 样式文件,加载的js,图片
│ site.js
│ widget.css

├─black

├─...

└─purple

[译] php 中更简洁的三元运算符 ?:

原文地址 Even shorter ternary operators in PHP using ?:

今天我发现了 PHP 三元运算符的一个小小的用法. 这给我干涸的大脑一点乐趣!

PHP 三元运算符是对参数赋值时候的一个简洁的主要用法. 一个主要的用法: PHP 三元运算符能够让你在一行代码中描述判定代码, 从而替换掉类似以下的代码:

1
2
3
4
5
if (isset($value)) {
$output = $value;
} else {
$output = 'No value set.';
}

使用以下代码替代:

1
$output = isset($value) ? $value : 'No value set.';

第二个代码例子是非常简洁的用法, 在多种情况下(并非所有), 这是一个非常实用的用法. 有许多关于是否应该使用三元运算符的争辩;让我说, 这就是一个工具, 向其他工具一样, 只是用的正确与否.

常用的语法是 (expression) ? value if truthy : value if falsy.这个表达式可以是一个变量, 测试这个变量是真还是假:

1
$output = $value ? $value : 'No value set.';

问题是: 以上的例子很常用同时也重复的有些烦人: 写两次 $value 就像是感到错误一样.

好在是, 我今天发现在 PHP 5.3 中介绍了一个更简洁的使用三元运算符的语法. 你可以从手册中学到, 但是这里我们怎么样让上边的例子更简洁呢:

1
$output = $value ?: 'No value set.';

这个看起来很熟悉, 这个是因为很像其他的简写运算符:

1
$value = $value . $other_value;

转换成:

1
$value .= $other_value;

为了更简洁, 这个意味着我们可以这样简写并不意味着我们就应该这么写. 但是, 当我们写简洁代码的时候, 这种方式会看起来更清楚, 我们应该这么写, (并且这个特性允许我们在多种情况下使用这个运算符[this feature allows us to DRY up the ternary operator in many cases])

Forms & HTML 组件 (laravelcollective/html)

原文地址: Laravel Collective Forms & HTML

安装

首先通过 composer 来安装这个 包, 编辑项目的 composer.json 文件. 在 require 部分 加入 laravelcollective/html :

1
2
3
"require": {
"laravelcollective/html": "5.1.*"
}

接下来从命令行更新 composer  :

1
composer update -vvv

接下来添加 provider 到 config/app.phpproviders 数组:

1
2
3
4
5
'providers' => [
// ...
Collective\Html\HtmlServiceProvider::class,
// ...
],

最后 添加两个类链接到 config/app.phpaliases 数组:

1
2
3
4
5
6
'aliases' => [
// ...
'Form' => Collective\Html\FormFacade::class,
'Html' => Collective\Html\HtmlFacade::class,
// ...
],

##创建表单

打开表单

1
2
3
{!! Form::open(array('url' => 'foo/bar')) !!}
//
{!! Form::close() !!}

默认是 POST 方法, 你可以随意指定其他接收方法

1
echo Form::open(array('url' => 'foo/bar', 'method' => 'put'))

Note: HTML 表单仅仅支持 POSTGET方法, PUTDELETE 方法将会使用一个隐藏域_method 添加到 form 表单中来欺骗实现

你可以使用指定的控制器@动作 或者命名的路由来创建表单

1
2
3
echo Form::open(array('route' => 'route.name'))

echo Form::open(array('action' => 'Controller@method'))

同样也可以向路由中传入参数.

1
2
3
echo Form::open(array('route' => array('route.name', $user->id)))

echo Form::open(array('action' => array('Controller@method', $user->id)))

如果你的表单需要支持文件上传, 在数组中添加 一个 files 配置项.

1
echo Form::open(array('url' => 'foo/bar', 'files' => true))

CSRF 保护

向表单中添加 CSRF Token

Laravel 提供了一个简单的方法来防止你的应用遭受跨站攻击. 首先会在你的 session 中生成一个随机的 token, 如果你使用 Form::open 方法并且提交方法是 POST, PUT或者是 DELETE,  CSRF token 将会自动的添加到你的 form 表单的隐藏域中. 换种方法 如果你像自己生成  CSRF token 字段, 你可以使用 token 方法.

1
echo Form::token();

给路由添加  CSRF 过滤器

1
2
3
4
Route::post('profile', array('before' => 'csrf', function()
{
//
}));

表单模型绑定

给表单绑定模型

通常, 你需要想表单中填入来自数据库模型的数据. 想这样做你可以使用 Form::model 方法.

1
echo Form::model($user, array('route' => array('user.update', $user->id)))

现在当你自动生成一个表单元素, 例如文本输入框. Model 的值将会自动匹配并且填写到相关的表单字段中.例如. 一个文本输入框的 name 是 email这个字段将会用 用户 Modelemail 属性来填充并且设置. 当然, 还有其他用法. 如果一个字段在 session 闪存数据中 也存在这个名字, 这个将会覆盖模型中的这个字段值. 优先级是这个样子的:

  1. Session Flash Data (Old Input) [session 闪存 / 老的输入数据 ]
  2. Explicitly Passed Value [输入值]
  3. Model Attribute Data [模型属性值]

这个可以让你快速的使用模型数据来创建表单, 也能轻松的在服务器校验错误之后重新发布表单.

Note: 使用 Form::model方法的时候一定要使用 Form::close来关闭表单!

标签

生成标签元素

1
echo Form::label('email', 'E-Mail Address');

指定额外的 html 属性

1
echo Form::label('email', 'E-Mail Address', array('class' => 'awesome'));

Note: 在创建了一个标签之后, 如果有创建的表单元素的 name 值和 label 的 name 值相符的话, 将会自动在 表单元素 中自动匹配增加 id 属性. id 的值就是 label 的 name 值.

文本框, 文本域, 密码 & 隐藏域

创建文本框

1
echo Form::text('username');

指定默认值

1
echo Form::text('email', 'example@gmail.com');

Note:  hiddentextarea 方法的参数和 text 相同.

生成密码输入框

1
echo Form::password('password', array('class' => 'awesome'));

生成其他输入框

1
2
echo Form::email($name, $value = null, $attributes = array());
echo Form::file($name, $attributes = array());

多选和单选

生成单选和多选

1
2
echo Form::checkbox('name', 'value');
echo Form::radio('name', 'value');

生成带有选中状态的表单元素

1
2
echo Form::checkbox('name', 'value', true);
echo Form::radio('name', 'value', true);

数字

生成数字输入框

1
echo Form::number('name', 'value');

日期

生成日期输入框

1
echo Form::date('name', \Carbon\Carbon::now());

文件选择器

生成文件选择器

1
echo Form::file('image');

Note: 表单中必须设置 files 参数的值为 true

下拉列表

生成下拉列表

1
echo Form::select('size', array('L' => 'Large', 'S' => 'Small'));

生成有默认值的下拉列表

1
echo Form::select('size', array('L' => 'Large', 'S' => 'Small'), 'S');

生成空占位符的 下拉列表

这回创建一个没有任何值的 <option> 元素作为下拉列表的第一个选择值.

1
echo Form::select('size', array('L' => 'Large', 'S' => 'Small'), null, ['placeholder' => 'Pick a size...']);

生成分组的列表

1
2
3
4
echo Form::select('animal', array(
'Cats' => array('leopard' => 'Leopard'),
'Dogs' => array('spaniel' => 'Spaniel'),
));

生成范围选择值的下拉列表

1
echo Form::selectRange('number', 10, 20);

生成有月份名称的选择值

1
echo Form::selectMonth('month');

按钮

生成提交按钮

1
echo Form::submit('Click Me!');

Note: 想创建一个按钮元素? 试用 button 方法. 他和 submit 方法有相同的参数.

自定义表单元素

注册一个新的表单元素

用来很方便的来自定义一个表单元素的方法叫做 macros . 合理是怎样使用它. 首先简单的使用名称和闭包函数来注册一个 :

1
2
3
4
Form::macro('myField', function()
{
return '<input type="awesome">';
});

现在你可以使用自定义的名字来调用这个 macro

调用自定义的 Form Macro

1
echo Form::myField();

生成 URL

根据给定的 URL 生成 html 链接

1
echo link_to('foo/bar', $title = null, $attributes = array(), $secure = null);

生成一个链接到指定资源的 html

1
echo link_to_asset('foo/bar.zip', $title = null, $attributes = array(), $secure = null);

生成一个根据给定路由的 html 链接

1
echo link_to_route('route.name', $title = null, $parameters = array(), $attributes = array());

根据指定的控制器/方法来生成 html 链接

1
echo link_to_action('HomeController@getIndex', $title = null, $parameters = array(), $attributes = array());

[译] Laravel-mix 4.0 中文文档

原文地址: Laravel Mix Docs

概览

升级

升级到 v4.0

1
2
npm remove laravel-mix
npm install laravel-mix@^4.0.0 --save-dev

升级后,如果遇到任何相关 vue-template-compiler 的问题,这与你安装的 vue 版本号 vue-template-compiler 版本号必须相同。更新其中一个或两个以解决此问题。

新特性

  • 编译速度更快。
  • 安装 npm 速度更快。
  • 升级到 webpack 4。
  • 升级到 vue-loader 15。
  • 升级到 Babel 7。
  • 自动第三方包获取。如果 mix.extract() 不使用参数调用,node_modules/ 将自动提取所有依赖项(您引入的任何包)。!
  • 可以提供 CSS 压缩(via cssnano)选项。
  • PostCSS 插件可以分别传递给 mix.sass/less/stylus() 。这意味着mix.sass() 如果需要,您可以为每个插件提供唯一的 PostCSS 插件。
  • JS optimizing/minification 从 Uglify 切换 Terser。
  • 从 node-sass 切换到 Dart Sass。虽然这会带来较小的编译时间成本,但 npm 安装的好处是更快,更可靠。
  • 改进的 Babel 配置合并策略。你现在可以通过 .babelrc 在项目根目录中创建文件来覆盖或调整通过 Mix 提供的任何默认 Babel 插件和预设。

修复

  • 由于升级到 webpack 4,所有 npm 警报都已修复。

备注

  • 如果你的项目大量使用 JavaScript 动态导入,你可能需要等到到明年初发布的 webpack 5。有与此相关的已知编译问题,在此之前我们无法解决。一旦 webpack 5 推出,Mix 将在不久后更新。如果您不熟悉动态导入,那么这很可能不会影响您的项目
  • Sass 支持现在是按需依赖。在混合之前的版本中,node-sass并且sass-loader依赖被列入开箱即用,不管你的项目是否需要 Sass 编译。为了帮助缩短安装时间,当且仅当您的项目指定了 Sass 编译时,才会按需安装这两个依赖项mix.sass()。第一次运行时npm run dev,将安装依赖项并将其保存到 dev-dependencies 列表中。

导入 ES 模块

作为 vue-loader 15 更新的一部分,如果您的代码使用 CommonJS 语法导入 EcmaScript 模块,则需要追加 .default,如下所示:

1
2
3
4
5
Vue.component(
'example-component',
- require('./components/ExampleComponent.vue')
+ require('./components/ExampleComponent.vue').default
);

建议切换到 EcmaScript 导入:

1
2
3
import ExampleComponent from './components/ExampleComponent.vue';

Vue.component('example-component', ExampleComponent);

Babel 7 支持

官方 Babel 插件的命名约定已经改变。
它们现在位于**@babel**命名空间下。

更新您的package.json并更改所有出现的"babel-plugin-[name]"

1
2
- "babel-plugin-[name]": "6.x"
+ "@babel/plugin-[name]": "7.x"

如果你在项目中创建了一个 .babelrc 文件,请更新所有插件名称引用:

1
2
- "plugins": ["babel-plugin-transform-object-rest-spread"]
+ "plugins": ["@babel/plugin-proposal-object-rest-spread"]

从 Node Sass 切换到 Dart Sass

如果我们从 node-sass 切换到 dart-sass,虽然在很大程度上是相同的,你可能会注意到在编译时改变或警告。您可以逐个解决这些问题,也可以手动切换回 node-sass,如下所示:

1
npm install node-sass
1
2
3
mix.sass('resources/sass/app.sass', 'public/css', {
implementation: require('node-sass')
});

删除了 fastSass()和 standaloneSass()

mix.fastSass()mix.standaloneSass()(别名)已被完全删除。
为了提高那些只需要编译 CSS 的人的性能,这个命令提供了与核心 webpack 构建分开的 Sass 编译。
然而,它给新手带来的更多的是困惑而不是有帮助。
mix.fastSass() 到迁移 mix.sass()

1
2
3
- mix.fastSass()
- mix.standaloneSass()
+ mix.sass()

删除已弃用的.mix属性

已弃用的 .mix 属性现已删除。
如果您有 require('laravel-mix').mix webpack.mix.js 文件,请将其更改为 require('laravel-mix')

1
2
- require('laravel-mix').mix
+ require('laravel-mix')

从 Uglify 切换到 Terser

由于强制从 Uglify 切换到 Terser,
如果您的项目覆盖了默认配置
Config.uglify = {},则需要切换到Config.terser = {}
选项 API在很大程度上是相同的。

webpack.mix.js:

1
2
3
4
5
6
7
8
9
mix.options({
- uglify: {
- uglifyOptions: {
+ terser: {
+ terserOptions: {
warnings: true
}
}
});

Vue 组件 Sass 预处理

如果您的项目不包含mix.sass()调用(自动下载所有必需的依赖项),但lang="sass"在 Vue 组件中指定,则可能需要安装 node-sass 或 sass。

因为 Mix 不知道你在 Vue 组件中指定了哪些预处理器,所以
需要手动将它们引入。

你可以解决这个问题:

1
npm install node-sass sass-loader

要么

1
npm install sass sass-loader

请注意,Less 和 Stylus 也是如此。

基本示例

larave-mix 是位于 webpack 顶层的一个简洁的配置层,在 80% 的情况下使用 laravel mix 会使操作变的非常简单。尽管 webpack 非常的强大,但大部分人都认为 webpack 的学习成本非常高。但是如果你不必用再担心这些了呢?

看一下基本的 webpack.mix.js 文件,让我们想象一下我们现在只需要编译 javascript(ES6)和 sass 文件:

1
2
3
4
let mix = require('laravel-mix');

mix.sass('src/app.sass', 'dist')
.js('src/app.js', 'dist');

怎么样,简单吗?

  1. 编译 sass 文件, ./src/app.sass./dist/app.css
       2. 打包在 ./src/app.js 的所有 js(包括任何依赖)到 ./dist/app.js

使用这个配置文件,可以在命令行触发 webpack 指令:node_modules/bin/webpack

在开发模式下,并不需要压缩输出文件,如果在执行 webpack 的时候加上环境变量:export NODE_ENV=production && webpack,文件会自动压缩

less ?

但是如果你更喜欢使用 Less 而不是 Sass 呢?没问题,只要把 mix.sass() 换成 mix.less()就 OK 了。

使用 laravel-mix,你会使发现大部分 webpack 任务会变得更又把握

安装

尽管 laravel-mix 对于 laravel 来做的优化工具,但也能被用于其他任何应用。

laravel 项目

laravel 已经包含了你所需要的一切,简易步骤:

  1. 安装 laravel

  2. 运行 npm install

  3. 查看 webpack.mix.js 文件 ,就可以开始使用了.

你可以在命令行运行 npm run watch 来监视你的前段资源改变,然后重新编译。

在项目根目录下并没有 webpack.config.js 配置文件,laravel 默认指向根目录下的配置文件。如果你需要自己配置它,你可以把它拷贝到根目录下,同时修改 package.json 里的 npm 脚本: cp node_modules/laravel-mix/setup/webpack.config.js ./.

独立项目

首先使用 npm 或者 yarn 安装 laravel-mix,然后把示例配置文件复制到项目根目录下

1
2
3
4
mkdir my-app && cd my-app
npm init -y
npm install laravel-mix --save-dev
cp -r node_modules/laravel-mix/setup/webpack.mix.js ./

现在你会有如下的目录结构

1
2
3
node_modules/
package.json
webpack.mix.js

webpack.mix.js 是你在 webpack 上层的配置文件,大部分时间你需要修改的是这个文件

首先看下 webpack.mix.js 文件

1
2
3
4
5
let mix = require('laravel-mix');

mix.js('src/app.js', 'dist')
.sass('src/app.scss', 'dist')
.setPublicPath('dist');

注意源文件的路径,然后创建匹配的目录结构(你也可以改成你喜欢的结构)。现在都准备好了,在命令行运行 node_modules/.bin/webpack 编译所有文件,然后你将会看到:

  • dist/app.css
  • dist/app.js
  • dist/mix-manifest.json(你的 asset 输出文件,稍后讨论)

干得漂亮!现在可以干活了。

NPM Scripts

把下面的 npm 脚本添加到你的 package.json 文件中可以加速你的工作操作.,laravel 安装的时候已经包含了这个东西了

1
2
3
4
5
6
7
8
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"hot": "cross-env NODE_ENV=development webpack-dev-server --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
}

laravel 工作流程

我们先回顾一下通用的工作流程以便你能在自己的项目上采用

1 . 安装 laravel

1
laravel new my-app

2 . 安装 Node 依赖

1
npm install

**3 . 配置 webpack.mix.js **

这个文件所有前端资源编译的入口

1
2
3
4
let mix = require('laravel-mix');

mix.js('resources/assets/js/app.js', 'public/js');
mix.sass('resources/assets/sass/app.scss', 'public/css');

默认会启用 JavaScript ES2017 + 模块绑定,就行 sass 编译一样。

4 . 编译

用如下指令编译

1
node_modules/.bin/webpack

也可以使用 package.json 里的 npm 脚本:

1
npm run dev

然后会看到编译好的文件:

  • ./public/js/app.js
  • ./public/css/app.css

监视前端资源更改:

1
npm run watch

Laravel 附带一个ExampleComponent.vue文件,你可以在 components 文件夹中找到:

1
./resources/js/components/ExampleComponent.vue

修改一下,然后等待操作系统通知,这表示编译已完成!

mix.browserSync('myapp.test') 当 Laravel 应用程序中的任何相关文件发生更改时,您还可以使用自动重新加载浏览器。

5 . 更新视图

laravel 自带一个欢迎页面,我们可以用这个来做示例,修改一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Laravel</title>

<link rel="stylesheet" href="{{ mix('css/app.css') }}" />
</head>
<body>
<div id="app">
<example-component></example-component>
</div>

<script src="{{ mix('js/app.js') }}"></script>
</body>
</html>

刷新页面,干得漂亮!

常见问题

laravel-mix 必须在 laravel 下使用吗?

不,在 laravel 下使用使最好的,但也可以用在别的任何项目

我的代码没有压缩

只有在 node 环境变量为生产环境时才会被压缩,这样会加速编译过程,但在开发过程中是不必要的,下面是在生成环境下运行 webpack 的示例

1
export NODE_ENV=production && webpack --progress --hide-modules

强烈推荐你把下面的 npm 脚本添加到你的 package.json 文件中,注意 laravel 已经包括了这些了

1
2
3
4
5
6
"scripts": {
"dev": "NODE_ENV=development webpack --progress --hide-modules",
"watch": "NODE_ENV=development webpack --watch --progress --hide-modules",
"hot": "NODE_ENV=development webpack-dev-server --inline --hot",
"production": "NODE_ENV=production webpack --progress --hide-modules"
},

我使用的是 VM,webpack 不能检测到我的文件变化

如果你在 VM 下执行 npm run dev,你会发现 webpack 并不能监视到你的文件改变。如果这样的话,有两种方式来解决这个

  1. 配置 webpack 检测文件系统的变化, 注意:检测文件系统是资源密集型操作并且很耗费电池的使用时长.
  2. 转发文件通过使用类似于 vagrant-fsnotify 之类的东西将通知发送给 VM。注意,这是一个 只有 Vagrant 才有的插件。

检测 VM 文件系统变化, 修改一下你的 npm 脚本,使用 --watch-poll--watch 标签,像这样:

1
2
3
"scripts": {
"watch": "NODE_ENV=development webpack --watch --watch-poll",
}

推送文件改动到 VM, 在主机安装 vagrant-fsnotify

1
vagrant plugin install vagrant-fsnotify

现在你可以配置 vargrant 来使用这个插件, 在 Homestead 中, 在你的 Homestead.yaml 文件类似于这样

1
2
3
4
5
6
7
8
folders:
- map: /Users/jeffrey/Code/laravel
to: /home/vagrant/Code/laravel
options:
fsnotify: true
exclude:
- node_modules
- vendor

一旦你的 vagrant 机器启动, 只需要在主机上运行 vagrant fsnotify 把文件的改动推送到 vm 上, 然后在 vm 内部运行 npm run watch 就能够检测到文件的改动了.

如果你还是有问题,去这儿溜达溜达吧

为什么在我的 css 文件里显示图片在 node_modules 里找不到

你可能用的是相对路径,但是在你的 resources/assets/sass/app.css 里并不存在:

1
2
3
body {
background: url('../img/example.jpg');
}

当引用相对路径的时候,会根据当前文件的路径来搜索,同样的,webpack 会首先搜索 `resources/assets/img/example.jpg ,如果找不到,会继续搜索文件位置,包括 node_modules,如果还找不到,就报错:

1
2
3
ERROR  Failed to compile with 1 errors

This dependency was not found in node_modules:

有两个解决办法:

1 . 让 resources/assets/img/example.jpg 存在这个文件.
2 . 编译 css 的时候添加下面的选项,禁用 css 的 url 处理:

1
2
3
mix.sass("resources/assets/sass/app.scss", "public/css").options({
processCssUrls: false,
});

他对老项目特别有用,因为你的文件夹结构已经完全创建好了。

我不想把 mix-manifest.json 文件放在项目根目录下

如果你没有使用 laravel,你的 mix-manifest.json 文件会被放到项目根目录下,如果你不喜欢的话,可以调用 mix.setPublicPath('dist/'),然后 manifest 文件就会被放到 dist 目录下。

怎样使用 webpack 自动加载模块

webpack 使用 ProvidePlugin 插件加载一些需要的模块,常用的一个例子就是加载 jQuery:

1
2
3
4
5
6
7
8
9
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
});

// in a module
$('#item'); // <= just works
jQuery('#item'); // <= just works
// $ is automatically set to the exports of module "jquery"

当 laravel-mix 自动加载模块的时候(像上面说的那样),你如果想禁用(传一个空对象)或者用你自己的模块覆盖它,可以调用 mix.autoload() 方法:

1
2
3
4
mix.autoload({
jquery: ['$', 'window.jQuery', 'jQuery'], // more than one
moment: 'moment' // only one
});

为什么我看到一个 “Vue packages version mismatch”错误

如果, 更新你的依赖, 你有以下编译失败的信息

1
2
3
4
5
6
Module build failed: Error:

Vue packages version mismatch:

* vue@2.5.13
* vue-template-compiler@2.5.15

这意味着你的 vuevue-template-compiler 依赖不同步, 每一个 Vue 的更新, 版本号必须是相同的. 更新来修复这个错误

1
2
3
4
5
npm update vue

// or

npm install vue@2.5.15

排障

我在更新/安装 mix 时候出现错误

不幸的是,你的依赖项可能没有正确安装的原因有无数个。一个常见的根本原因是安装了老版本的 Node(node -v) 和 npm (npm -v)。第一步,访问 http://nodejs.org 并更新它们。

否则,它通常与需要删除的错误锁文件有关。让这一系列命令尝试从头开始安装一切

1
2
3
4
rm -rf node_modules
rm package-lock.json yarn.lock
npm cache clear --force
npm install

为什么 webpack 不能找到我的 app.js 条目文件?

如果你遇到这样的失败信息……

1
2
3
These dependencies were not found:

* /Users/you/Sites/folder/resources/assets/js/app.js

… 你可能使用 npm 5.2 (npm -v) 这个版本。这个版本引入了一个导致安装错误的错误。该问题已被在 npm 5.3 修复。请升级,然后重新安装

1
2
3
4
rm -rf node_modules
rm package-lock.json yarn.lock
npm cache clear --force
npm install

API

Javascript

1
mix.js(src|[src], output)

简单的一行代码,larave mix 可以执行很多重要的操作

  • ES2017+ 模块编译
  • 创建并且编译 vue 组件(通过 vue-loader)
  • 模块热替换(HMR)
  • Tree-shaking 打包技术,webpack2 里新增的(移除无用的库)
  • 提取和拆分 vendor 库(通过mix.extract()方法), 使长期缓存变的容易
  • 自动版本化(文件哈希),通过 mix.version()

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let mix = require('laravel-mix');

// 1. A single src and output path.
mix.js('src/app.js', 'dist/app.js');


// 2. For additional src files that should be
// bundled together:
mix.js([
'src/app.js',
'src/another.js'
], 'dist/app.js');


// 3. For multiple entry/output points:
mix.js('src/app.js', 'dist/')
.js('src/forum.js', 'dist/');

laravel 示例

考虑到典型的 laravel 默认安装的时候会把入口定位在 ./resources/assets/js/app.js,所以我们先准备一个 webpack.mix.jsapp.js 编译到 ./public/js/app.js

1
2
3
let mix = require('laravel-mix');

mix.js('resources/assets/js/app.js', 'public/js');

现在上面所有的项你都可以用了,只需要调用一个方法。

在命令行调用 npm run dev 执行编译。

Vue 组件

laravel mix 包罗万象,支持 vue 组件编译,如果你不使用 vue 的话,可以忽略这块。

单文件组件是 vue 最重要的特征。在一个文件里为一个组件定义模板,脚本,样式表。

./resources/assets/js/app.js

1
2
3
4
5
6
7
import Vue from "vue";
import Notification from "./components/Notification.vue";

new Vue({
el: "#app",
components: { Notification },
});

在上面,我们导入了 vue(首先你需要执行npm install vue --save-dev安装 vue),然后引入了一个叫 Notification 的 vue 组件并且注册了 root vue 实例。

./resources/asset/js/components/Notification.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="notification">
{{ body }}
</div>
</template>

<script>
export default {
data() {
return {
body: 'I am a notification.'
}
}
}
</script>
<style>
.notification {
background: grey;
}
</style>

如果你了解 vue,这些你都会很熟悉,继续。

./webpack.mix.js

1
2
3
let mix = require('laravel-mix');

mix.js('resources/assets/js/app.js', 'public/js');

执行 npm run dev 编译文件,这样就简单的创建了一个 HTML 文件,引入 ./js/app.js 文件,然后在浏览器里查看吧!

React 支持

laravel mix 也装载了基本的 react 支持,只要把 mix.js()改成 mix.react()并且使用相同的参数。在底层,mix 会引用 react 需要的任何 babel 插件。

1
mix.react('resources/assets/js/app.jsx', 'public/js/app.js');

当然,你仍然需要使用 npm 安装 react 和 reactDOM,不过要注意小心行事。

Typescript 支持

Laravel Mix 还附带基本的 Typescript 支持。
只需更新您的 mix.js() 调用 mix.ts(),然后使用完全相同的参数集。

1
mix.ts('resources/assets/js/app.ts', 'public/js/app.js');

当然,你仍然希望做必要的调整,如创建tsconfig.json文件和安装DefinitelyTyped,但其他一切都应该在开发郭晨他提供中考虑到了。

库代码分离

1
mix.js(src, output).extract();

把所有的 js 都打包成一个文件会伴随着潜在的风险:每次更新项目中就算很小的一部分都需要破坏所有用户的缓存,这意味着你的第三方库需要重新被下载和缓存。这样很不好。

一个解决的办法是分离或者提取你的库文件。

  • 应用代码: app.js
  • vendor 库: vendor.js
  • Manifest(webpack runtime): manifest.js
1
mix.extract();

or

1
mix.extract(['vue', 'jquery']);

extract 方法接受一个你想要从打包文件里提取出的库的数组,使用这个方法,Vue 和 jQuery 的源代码都会被放在 vendor.js 里。如果在未来你需要对应用代码做一些微小的变动,并不会对大的 vendor 库产生影响,它们依然会留在长期缓存。

一旦执行 webpack 打包文件,你会发现三个新的文件,你可以在 HTML 页面引用它们。

1
2
3
<script src="/js/manifest.js"></script>
<script src="/js/vendor.js"></script>
<script src="/js/app.js"></script>

实际上,我们付出了一些 HTTP 请求的代价(就是会多请求几次)来换取长期缓存的提升。

Manifest 文件是什么

webpack 编译的时候会有一些 run-time 代码协助其工作。如果没有使用 mix.extract(),这些代码你是看不到的,它会在打包文件里,然而,如果我们分离了代码并且允许长期缓存,在某些地方就需要这些 run-time 代码,所以,mix 会把它提取出来,这样一来,vendor 库和 manifest 文件都会被缓存很长时间。

浏览器自动刷新

1
mix.browserSync('my-site.test');

BrowserSync 能自动监控文件变动并且把你的变化通知浏览器, – 完全不需要手动刷新。你可以调用 mix.browserSync() 方法来开启这个功能:

1
2
3
4
5
6
7
8
mix.browserSync('my-domain.test');

// Or:

// https://browsersync.io/docs/options/
mix.browserSync({
proxy: 'my-domain.test'
})

参数可以传字符串(proxy),也可以传对象(BrowserSync 设置)。你声明的域名作为 proxy 是非常重要的,Browsersync 将通过代理网址来输出到你的虚拟主机(webpack dev server).

其他选项可以从 Browsersync Documentation

现在, 启动 dev server (npm run watch), 并进行下一步操作吧.

模块热替换

laravel mix 对模块热替换提供了无缝的支持。

模块热替换(或者叫热加载),意思就是当 javascript 改变刷新页面的时候可以维持组件的状态,例如现在有一个计数器,按一下按钮,计数器会加 1,想象一下你点了很多次然后修改一下组件的相关文件,浏览器会实时的反映出你所做出的更改而保持计数器不变,计数器不会被重置,这就是热加载的意义最在。

在 laravel 里的用法

Laravel 和 Laravel 一起工作, 来抽象出热加载的复杂性.

看一下 laravel 里的 package.json 文件,在 scripts 模块,你可以看到:

1
2
3
4
5
6
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --progress --hide-modules",
"watch": "cross-env NODE_ENV=development webpack --watch --progress --hide-modules",
"hot": "cross-env NODE_ENV=development webpack-dev-server --inline --hot",
"production": "cross-env NODE_ENV=production webpack --progress --hide-modules"
}

注意一下 hot 选项,这个地方就是你所需要的,在命令行执行 npm run hot 会启动一个 node 服务器并且监视你的 bundle 文件,接下来,在浏览器打开你的 laravel 应用,一般应该是 http://my-app.test

在 laravel 应用里使用热加载很重要的一点是要保证所有的脚本资源引用的是前面启动的 node 服务器的 url:http://localhost:8080,现在你可以手动更新你的 HTML\Blade 文件了:

1
2
3
4
<body>
<div id="app">...</div>
<script src="http://localhost:8080/js/bundle.js"></script>
</body>

假设你有一些组件,尝试在浏览器里更改他们的状态,然后更新他们的模板文件,你可以看到浏览器会立刻反应出你的更新,但是状态并没有被改变。

但是,在开发部署环境下手动更新 url 会是一个负担,所以,laravel 提供了一个 mix()方法,他会动态的构建 js 或者样式表的引用,然后输出。上面的代码因此可以修改成:

1
2
3
4
5
<body>
<div id="app"></div>

<script src="{{ mix('js/bundle.js') }}"></script>
</body>

调整之后,Laravel 将为你做这项工作。如果运行 npm run hot 以启用热重加载,则该函数将设置必要的 http://localhost:8080 作为 URL。相反,如果您使用 npm run devnpm run pro,它将使用域名作为基准 url。

在 Https 中使用
如果你在 HTTPS 连接上开发你的应用,你的热重加载脚本和样式也必须通过 HTTPS 服务。要实现这一点,可以将 -—https 标志添加到 package.json 中的热选项命令中。

1
2
3
"scripts": {
"hot": "NODE_ENV=development webpack-dev-server --inline --hot --https",
}

通过上面的设置,webpack-dev-server 将会生成一个自签名证书。如果你希望使用自己的证书,可以使用以下设置:

1
"hot": "NODE_ENV=development webpack-dev-server --inline --hot --https --key /path/to/server.key --cert /path/to/server.crt --cacert /path/to/ca.pem",

现在在你的 Html/Blade 文件中可以使用

1
<script src="https://localhost:8080/js/bundle.js"></script>

或者

1
<script src="{{ mix('js/bundle.js') }}"></script>

在 spa 里的用法

laravel mix 包含了流行的 vue-loader 包,这意味着,如果是单页应用,你什么都不需要做,它是开箱即用的。

版本化

1
2
mix.js('src', 'output')
.version([]);

为了帮助长期缓存,Laravel Mix 提供了 mix.version() 方法,它支持文件散列。比如app.js?id=8e5c48eadbfdd5458ec6。这对清除缓存很有用。假设你的服务器自动缓存了一年的脚本,以提高性能。这很好,但是,每当您对应用程序代码进行更改时,需要一些方法来告诉用户更新缓存, 这通常是通过使用查询字符串或文件哈希来完成的。

启用了版本控制之后,每次代码更改时,都会生成一个新的散列查询字符串文件。看以下webpack.mix.js 文件

编译后,你会在 mix-manifest.json 文件看到 /css/app.css?id=5ee7141a759a5fb7377a/js/app.js?id=0441ad4f65d54589aea5。当然,你的特定散列将是唯一的。每当你调整 JavaScript 时,编译后的文件将会收到一个新的散列名称,这将有效地破坏缓存,一旦被推到生产环境中。

举个例子,试试 webpack --watch,然后修改一下你的 JavaScript。你将立即看到一个新生成的打包文件和样式表。

导入版本文件

这就引出了一个问题:如果名称不断变化,我们如何将这些版本化的脚本和样式表包含到 HTML 中呢?是的,这很棘手。答案将取决于你构建的应用程序的类型。对于 SPA,你可以动态地读取 Laravel Mix 生成的 manifest.json 文件,提取资料文件名(这些名称将被更新,以反映新的版本文件),然后生成 HTML。

Laravel 用户

对于 Laravel 项目,一个解决方案是开箱即用的。只需调用全局 mix() 函数,就完成了!我们将计算出导入的适当文件名。这里有一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>App</title>
<link rel="stylesheet" href="{{ mix('css/app.css') }}" />
</head>
<body>
<div id="app">
<h1>Hello World</h1>
</div>

<script src="{{ mix('js/app.js') }}"></script>
</body>
</html>

将未散列的文件路径传递给 mix() 函数,然后在后端,我们将弄清楚应该导入哪个脚本或样式表。请注意,你可能/应该使用这个函数,即使没有对文件进行版本控制。

版本化附加文件

mix.version() 将自动生成编译后的 JavaScript、Sass/Less 或合并文件。但是,如果你还希望将额外的文件作为构建的一部分,简单地传递路径或路径数组,就像这样:

1
mix.version(['public/js/random.js']);

现在,我们会版本化任何相关的编译文件,但是我们还会附加一个查询字符串,public/js/random.js?{hash},并更新 mix-manifest.json 文件。

Css 预处理器

1
2
3
4
5
mix.sass('src', 'output', pluginOptions);
mix.standaloneSass('src', 'output', pluginOptions); // Isolated from Webpack build.
mix.less('src', 'output', pluginOptions);
mix.stylus('src', 'output', pluginOptions);
mix.postCss('src', 'output', [ require('precss')() ])

一个单一的方法调用允许你编译你的 Sass,Less,或 Stylus 文件,同时自动应用 CSS3 前缀。

虽然 webpack 可以将所有 CSS 直接插入到绑定的 JavaScript 中,但 Laravel Mix 会自动执行必要的步骤,将其提取到你想要的输出路径中。

多构建

如果你需要编译多个顶级文件,你可以根据需要调用 mix.sass()(或任何一个预处理器变体). 对于每个调用,webpack 将输出一个包含相关内容的新文件。

1
2
mix.sass('src/app.scss', 'dist/') // creates 'dist/app.css'
.sass('src/forum.scss', 'dist/'); // creates 'dist/forum.css'

例子

让我们看一个例子

webpack.mix.js

1
2
3
let mix = require('laravel-mix');

mix.sass('resources/assets/sass/app.sass', 'public/css');

./resources/assets/sass/app.sass

1
2
3
4
$primary: grey

.app
background: $primary

Tip : 对于 Sass 编译, 你可以使用 .sass.scss 语法

像往常一样运行 npm run webpack 进行编译. 你会发现 ./public/css/app.css 文件包含

1
2
3
.app {
background: grey;
}

插件选项

编译的时候, Laravel Mix 的首先是去分别的调用 Node-Sass, Less,和 Slylus 来编译你的 Sass, Less 文件。有时,你可能需要重写我们传递给它们的默认选项。可以将它们作为第三个参数提供给 mix.sass(), mix.less()mix.stylus()

Stylus 插件
如果使用 Stylus, 你可能希望安装额外的插件,比如 Rupture 。运行 npm install rupture 来安装这个插件,然后在你的 mix.stylus() 中调用, 例如:

1
2
3
4
5
mix.stylus('resources/assets/stylus/app.styl', 'public/css', {
use: [
require('rupture')()
]
});

如果希望更深一步,并且在全局中自动导入插件,您可以使用 import 选项。这里有一个例子:

1
2
3
4
5
6
7
8
9
10
11
mix.stylus('resources/assets/stylus/app.styl', 'public/css', {
use: [
require('rupture')(),
require('nib')(),
require('jeet')()
],
import: [
'~nib/index.styl',
'~jeet/jeet.styl'
]
});

就是这个样子滴!

CSS url() 重写

一个关键的 webpack 概念是,它将重写你的样式表中的任何 url()。虽然这可能最初听起来很奇怪,但它是一项非常强大的功能。

一个例子

假设我们想要编译一些 Sass,其中包含一个图像的相对 url。

1
2
3
.example {
background: url("../images/thing.png");
}

提示:url()的绝对路径将被排除在 url 重写之外。因此,url('/images/thing.png')url('http://example.com/images/thing.png') 将不会被更改。

注意,这里说的是相对 URL? 默认情况下,Laravel Mix 和 webpack 将会找到 thing.png ,将其复制到 public/images 文件夹中,然后在生成的样式表中重写 url()。因此,编译的 CSS 将是:

1
2
3
.example {
background: url(/images/thing.png?d41d8cd98f00b204e9800998ecf8427e);
}

这也是 webpack 的一个很酷的特性。然而,它确实有一种倾向,让那些不理解 webpack 和 css-loader 插件如何工作的人感到困惑。你的文件夹结构可能已经是您想要的了,而且你希望不要修改那些url()。如果是这样的话,我们确实提供了一个覆盖方式:

1
2
3
4
mix.sass('src/app.scss', 'dist/')
.options({
processCssUrls: false
});

把这个加上你的 webpack.mix.js 文件中,我们将不再匹配 url() 或复制资源到你的公共目录。因此,编译后的 CSS 将与你输入时一样:

1
2
3
.example {
background: url("../images/thing.png");
}

这样的好处是,当禁用 url 处理时,您的 Webpack, Sass 编译和提取可以更快地编译。

PostCSS 插件

默认情况下,Mix 将通过流行的 Autoprefixer PostCSS plugin 将所有的 CSS 文件连接起来。因此,你可以自由使用最新的 CSS 3 语法,并理解我们将自动应用任何必要的浏览器前缀。在大多数情况下,默认设置应该就可以,但是,如果你需要调整底层的自动修复程序配置,那么如下所示:

1
2
3
4
5
6
7
8
9
10
mix.sass('resources/assets/sass/app.scss', 'public/css')
.options({
autoprefixer: {
options: {
browsers: [
'last 6 versions',
]
}
}
});

另外,如果你想完全禁用它——或者依赖一个已经包含 自动前缀的 PostCSS 插件:

1
2
mix.sass('resources/assets/sass/app.scss', 'public/css')
.options({ autoprefixer: false });

但是,你可能想要在构建中应用额外的 PostCSS 插件。木问题, 只需在 NPM 中安装所需的插件,然后在 webpack.mix.js 文件中引用,如下所示:

1
2
3
4
5
6
mix.sass('resources/assets/sass/app.scss', 'public/css')
.options({
postCss: [
require("postcss-custom-properties")
]
});

完成了!现在可以使用和编译自定义 CSS 属性(如果这是您的东西)。例如,如果 resources/assets/sass/app.scss 包含…

1
2
3
4
5
6
7
:root {
--some-color: red;
}

.example {
color: var(--some-color);
}

编译完成将会是

1
2
3
.example {
color: red;
}

PostCss 不适用 Sass/Less

或者,如果你更喜欢跳过 Sass/Less/Stylus 编译步骤,而是使用 PostCSS,你可以通过mix.postCss() 方法来完成。

1
2
3
mix.postCss('resources/assets/css/main.css', 'public/css', [
require('precss')()
]);

请注意,第三个参数是应该应用于你构建的 postcss 插件的数组。

独立 Sass 构建

如果你不希望 Mix 和 Webpack 以任何方式处理你的 Sass 文件,你可以使用mix.standaloneSass(),这将大大改善你应用程序的构建时间。请记住:如果你选择了这条路线,Webpack 不会触及你的 CSS。它不会重写 url,复制资源(通过 file-loader),或者应用自动图像优化或 CSS 清理。如果这些特性对于你的应用程序来说是没有必要的,那么一定要使用这个选项而不是mix.sass()

1
mix.standaloneSass('resources/assets/sass/app.scss', 'public/css');

注意:如果你正在使用 standaloneSass,在使用 npm run watch 进行文件更改时,你将需要使用下划线来前缀导入的文件,以将它们标记为依赖文件(例如,_header.scss _alert.scss)。如果不这样做,将导致 Sass 编译错误和/或额外的 CSS 文件。

文件复制

1
2
3
4
mix.copy(from, to);
mix.copy('from/regex/**/*.txt', to);
mix.copy([path1, path2], to);
mix.copyDirectory(fromDir, toDir);

有时, 你需要复制一个或者多个文件作为构建过程的一部分。没有问题, 这是小事一桩。使用mix.copy() 方法指定源文件或文件夹,然后指定您想要的目标文件夹/文件

1
mix.copy('node_modules/vendor/acme.txt', 'public/js/acme.txt');

在编译时,’acme’ 文件将被复制到 ‘public/js/acme.txt’。这是一个常见的用例,当你希望将一组字体通过 NPM 安装到 public 目录时。

系统通知

默认情况下,Laravel Mix 将显示每个编译的系统通知。这样,你就可以快速查看是否有需要查询的错误。但是,在某些情况下,这是不可取的(例如在生产服务器上编译)。如果发生这种情况,它们可以从你的 webpack.mix.js 文件中禁用。

1
2
mix.js(src, output)
.disableNotifications();

文件组合和最小化

1
2
3
4
mix.combine(['src', 'files'], 'destination');
mix.babel(['src', 'files'], destination);
mix.minify('src');
mix.minify(['src']);

如果使用得当,Laravel Mix 和 webpack 应该负责所有必要的模块捆绑和最小化。但是,你可能有一些遗留代码或第三方库需要连接和最小化, 这并不是一个问题。

组合文件

考虑下面的代码片段:

1
mix.combine(['one.js', 'two.js'], 'merged.js');

这自然会合并 one.jstwo.js 到一个单独的文件,叫做 merged.js。与往常一样,在开发期间,合并文件将保持未压缩状态。但是,对于生产(export NODE_ENV=production),这个命令将会最小化 merged.js

组合文件与 Babel 编译。

如果需要组合 使用 ES2015 方法编写的 JavaScript 文件,你可以更新的 mix.combine() 调用 mix.babel()。方法签名相同。唯一的区别是,在将文件组合起来之后,Laravel Mix 将对结果进行 Babel 编译,从而将代码转换成所有浏览器都能理解的 JavaScript 代码。

1
mix.babel(['one.js', 'two.js'], 'merged.js');

最小化文件

同样,你也可以使用 mix.minify() 命令缩小一个或多个文件。

1
2
mix.minify('path/to/file.js');
mix.minify(['this/one.js', 'and/this/one.js']);

这里有一些值得注意的事情:

  • 该方法将创建一个额外的 *.min.ext 文件。因此,压缩 app.js 将生成 app.min.js
  • 再一次声明,压缩只会在生产过程中发生。(export NODE_ENV=production)。
  • 不需要调用 mix.combine(['one.js', 'two.js'], 'merged.js').minify('merged.js'); ,只使用单一的 mix.combine() 调用。它会兼顾两者。

    重要:请注意,压缩只适用于 CSS 和 JavaScript 文件。minifier 不理解任何其他提供的文件类型。

自动加载

1
2
3
mix.autoload({
jquery: ['$', 'window.jQuery']
});

Webpack 提供了必要的功能,可以在 Webpack 所要求的每个模块中把一个模块作为变量。如果你使用的是一个特定的插件或库,它依赖于一个全局变量,例如 jQuery, mix.autoload() 可能会对你有用。

考虑下面的例子:

1
2
3
mix.autoload({
jquery: ['$', 'window.jQuery']
});

该代码片段指定 webpack 应该将 var $ = require('jquery') 添加到它所遇到的全局$标识符或 window.jQuery 中。漂亮!

事件钩子

1
mix.then(function () {});

你可能需要监听每次 webpack 完成编译的事件。也许你需要手动应用一些适合你的应用程序的逻辑。如果是这样,您可以使用 mix.then() 方法来注册任何回调函数。这里有一个例子:

1
2
3
4
mix.js('resources/assets/js/app.js', 'public/js')
.then(() => {
console.log('webpack has finished building!');
});

回调函数将通过 webpack Stats 对象,允许对所执行的编译进行检查:

1
2
3
4
5
mix.js('resources/assets/js/app.js', 'public/js')
.then((stats) => {
// array of all asset paths output by webpack
console.log(Object.keys(stats.compilation.assets));
});

可以在这里找到 Stats 对象的官方文档 : https://github.com/webpack/docs/wiki/node.js-api#stats

快速 webpack 配置

1
mix.webpackConfig({} || cb);

当然,你可以自由编辑提供的 webpack.config.js 文件,在某些设置中,更容易直接从你的 webpack.mix.js 修改或覆盖默认设置。对于 Laravel 应用来说尤其如此,默认情况下是在项目根文件夹中没有 webpack.config.js

例如,你可能希望添加一个由 webpack 自动加载的模块的自定义数组。在这个场景中,您有两个选项:

  • 根据需要编辑你的 webpack.config.js 文件
  • 在你的 webpack.mix.js 中调用 mix.webpackConfig() 文件,并传递重写参数。然后混合将进行一个深度合并。
    下面,作为一个示例,我们将为 Laravel Spark 添加一个自定义模块路径。
1
2
3
4
5
6
7
8
mix.webpackConfig({
resolve: {
modules: [
'node_modules',
path.resolve(__dirname, 'vendor/laravel/spark/resources/assets/js')
]
}
});

使用回调函数

当传递回调函数时,你可以访问 webpack 及其所有属性。

1
2
3
4
5
6
7
8
9
10
11
mix.webpackConfig(webpack => {
return {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
})
]
};
});

扩展 Mix

基于组件的系统 Mix 使用场景构建它的 API,你也可以访问—是否为你的项目扩展 Mix,或者作为一个可重用的包分发到世界。

可以在扩展页面找到已有的扩展列表。

例子

1
2
3
4
5
6
7
8
9
// webpack.mix.js;
let mix = require('laravel-mix');

mix.extend('foo', function(webpackConfig, ...args) {
console.log(webpackConfig); // the compiled webpack configuration object.
console.log(args); // the values passed to mix.foo(); - ['some-value']
});

mix.js('src', 'output').foo('some-value');

在上面的示例中,我们可以看到 mix.extend() 接受两个参数:在定义组件时应该使用的名称,以及一个回调函数或类,这些函数注册并组织必要的 webpack 逻辑。在后台,一旦构建了底层 webpack 配置对象,Mix 将触发这个回调函数。这将给你一个机会来插入或覆盖任何必要的设置。

虽然简单的回调函数可能对快速扩展很有用,但在大多数情况下,您可能希望构建一个完整的组件类,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mix.extend(
'foo',
new class {
register(val) {
console.log('mix.foo() was called with ' + val);
}

dependencies() {}

webpackRules() {}

webpackPlugins() {}

// ...
}()
);

在扩展 Mix 时,通常需要触发一些指令:

  • 安装这些依赖关系。
  • 将此规则/加载程序添加到 webpack 中。
  • 包含这个 webpack 插件。
  • 完全覆盖 webpack 配置的这一部分。
  • 将此配置添加到 Babel。
  • 等。

这些操作中的任何一个都是带有 Mix 组件系统有联系。

组件的接口

  • name:当调用组件时,应该使用什么作为方法名。(默认为类名。)
  • dependencies:列出应该由 Mix 安装的所有 npm 依赖项。
  • register:当您的组件被调用时,所有的用户参数将立即被传递给这个方法。
  • boot:启动组件。这个方法是在用户的 webpack.mix 之后触发的。js 文件已经加载完毕。
  • webpackEntry:附加到主混合 webpack 入口对象。
  • webpackRules:与主 webpack 加载器合并的规则。
  • webpackplugin:与主 webpack 配置合并的插件。
  • webpackConfig:覆盖生成的 webpack 配置。
  • babelConfig:额外的 Babel 配置应该与 Mix 的默认值合并。

这里有一个示例/虚拟组件,它可以让你更好地了解如何构建自己的组件。更多的例子,请参考在后台 Mix 使用的组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
class Example {
/**
* The optional name to be used when called by Mix.
* Defaults to the class name, lowercased.
*
* Ex: mix.example();
*
* @return {String|Array}
*/
name() {
// Example:
// return 'example';
// return ['example', 'alias'];
}

/**
* All dependencies that should be installed by Mix.
*
* @return {Array}
*/
dependencies() {
// Example:
// return ['typeScript', 'ts'];
}

/**
* Register the component.
*
* When your component is called, all user parameters
* will be passed to this method.
*
* Ex: register(src, output) {}
* Ex: mix.yourPlugin('src/path', 'output/path');
*
* @param {*} ...params
* @return {void}
*
*/
register() {
// Example:
// this.config = { proxy: arg };
}

/**
* Boot the component. This method is triggered after the
* user's webpack.mix.js file has executed.
*/
boot() {
// Example:
// if (Config.options.foo) {}
}

/**
* Append to the master Mix webpack entry object.
*
* @param {Entry} entry
* @return {void}
*/
webpackEntry(entry) {
// Example:
// entry.add('foo', 'bar');
}

/**
* Rules to be merged with the master webpack loaders.
*
* @return {Array|Object}
*/
webpackRules() {
// Example:
// return {
// test: /\.less$/,
// loaders: ['...']
// });
}

/*
* Plugins to be merged with the master webpack config.
*
* @return {Array|Object}
*/
webpackPlugins() {
// Example:
// return new webpack.ProvidePlugin(this.aliases);
}

/**
* Override the generated webpack configuration.
*
* @param {Object} webpackConfig
* @return {void}
*/
webpackConfig(webpackConfig) {
// Example:
// webpackConfig.resolve.extensions.push('.ts', '.tsx');
}

/**
* Babel config to be merged with Mix's defaults.
*
* @return {Object}
*/
babelConfig() {
// Example:
// return { presets: ['@babel/preset-react'] };
}
}

请注意,上面示例中的每个方法都是可选的。在某些情况下,您的组件可能只需要添加一个 webpack 加载程序和/或调整混合使用的 Babel 配置。没有问题的话省略其余的接口。

1
2
3
4
5
6
7
8
class Example {
webpackRules() {
return {
test: /\.test$/,
loaders: []
};
}
}

现在,当 Mix 构造底层 webpack 配置时,你的规则将包含在生成的webpackConfig.module.rules 数组中。

使用

一旦你构建或安装了想要的组件,只需从 webpack.mix.js 中获取它即可,你都准备好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// foo-component.js

let mix = require('laravel-mix');

class Example {
webpackRules() {
return {
test: /\.test$/,
loaders: []
};
}
}

mix.extend('foo', new Example());
1
2
3
4
5
6
7
8
9
// webpack.mix.js

let mix = require('laravel-mix');
require('./foo-component');

mix
.js('src', 'output')
.sass('src', 'output')
.foo();

自定义方法

LiveReload

现在 Laravel Mix 与 Browsersync 已经支持了开箱即用,但是你可能更喜欢使用 LiveReload, 当检测到修改时,LiveReload 可以自动监视您的文件并刷新页面。

安装 webpack-livereload-plugin

1
npm install webpack-livereload-plugin@1 --save-dev

配置 webpack.mix.js

将以下几行添加到 webpack.mix.js 底部。

1
2
3
4
5
6
7
var LiveReloadPlugin = require('webpack-livereload-plugin');

mix.webpackConfig({
plugins: [
new LiveReloadPlugin()
]
});

虽然 LiveReload 有她很好用的默认值,但是这里可以查看一个可用的插件选项列表

安装 LiveReload.js

最后,我们需要安装 LiveReload.js。您可以通过 LiveReload Chrome 插件,或者在你的主要站点模板的关闭</body>标记之前添加以下代码:

1
2
3
@if(config('app.env') == 'local')
<script src="http://localhost:35729/livereload.js"></script>
@endif

运行 dev server

1
npm run watch

现在,LiveReload 将自动监控您的文件并在必要时刷新页面。享受吧!

Jquery UI

jQuery UI 是一个用于呈现公共组件的工具包,比如 datepickers、draggables 等。不需要做任何调整,以使其与 Laravel Mix 一起工作。

构建 webpack.mix.js 配置

1
2
mix.js('resources/assets/js/app.js', 'public/js')
.sass('resources/assets/sass/app.scss', 'public/css');

安装 jquery-ui

1
npm install jquery-ui --save-dev

加载必要插件

1
2
3
4
5
6
// resources/assets/js/app.js

import $ from 'jquery';
window.$ = window.jQuery = $;

import 'jquery-ui/ui/widgets/datepicker.js';

加载 CSS

1
2
3
// resources/assets/sass/app.scss

@import '~jquery-ui/themes/base/all.css';

触发 UI 组件

1
2
// resources/assets/js/app.js
$('#datepicker').datepicker();

高级配置

Laravel Mix 配置项

1
2
3
4
5
6
7
8
9
10
11
12
mix.options({
extractVueStyles: false,
processCssUrls: true,
terser: {},
purifyCss: false,
//purifyCss: {},
postCss: [require('autoprefixer')],
clearConsole: false,
cssNano: {
// discardComments: {removeAll: true},
}
});

如果需要的话可以使用一些混合选项和覆盖选项。请注意上面的选项,以及它们的默认值。这里有一个快速概述:

  • **extractVueStyles:**提取 .vue 组件样式(CSS 在 <style> 标签内)到一个专用文件,而不是将其嵌入到 HTML 中。
  • **globalVueStyles:**表示一个文件包含在每个组件样式中。这个文件应该只包含变量、函数或 mixin,以便在最终的编译文件中防止重复的 css。这个选项只有在启用了提取工具时才有效。
  • **processCssUrls:**进程/优化相对样式表url()。默认情况下,Webpack 会自动更新这些 url。但是,如果您的文件夹结构已经按照您想要的方式进行组织,那么将此选项设置为 false 以禁用处理。
  • terser: 使用此选项合并项目所需的任何自定义 Terser 选项
  • **purifyCss:**如果你想要混合自动读取你的 HTML/Blade 文件,并删除你的 CSS 包,你可以将这个选项设置为 true。您还可以传递包含 purifycss-webpack 选项的对象。
  • **postCss:**合并任何自定义的 postCss 插件。
  • **clearConsole:**设置为 false,如果您不想在每次构建后清除终端/控制台。
  • cssNano: 使用此选项设置项目所需的cssnano 选项

开发协议

MIT

Copyright (c) 2018 Jeffrey Way jeffrey@jeffrey-way.com

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

[转] Laravel5 插件包 vendor 开发

原文地址: laravel5.2插件包vendor开发

准备工作

1
2
3
4
1)拥有git账户密码,熟悉git常用命令
2)拥有packagist账户密码
3)本地安装了composer
4)必须laravel5.2版本且有一定的基础,了解服务提供者/门面概念

1)新建文件夹

在新建的laravel项目中建立如下目录:

1
packages/yuansir/toastr/src

packages 目录和 app 目录同级。
我们开发包的代码都放在这个src目录中,yuansir和toastr完全自定义。


2)修改项目根目录的composer.json

修改项目的composer.json,设定PSR-4命名空间:

1
2
3
4
5
6
7
8
9
"autoload": {
"classmap": [
"database"
],
"psr-4": {
"App\\": "app/",
"Yuansir\\Toastr\\": "packages/yuansir/toastr/src/"
}
},

3)重新生成autoload文件

根目录下cmd执行

1
composer dump-autoload

4)扩展包composer.json

cmd切换到插件目录:packages/yuansir/toastr 执行命令,根据提示填写

1
composer init

填写完基本信息之后 在packages/geekghc/laraflash目录下就会生成一个composer.json文件


5)创建服务提供者Service Provider

1
php artisan make:provider ToastrServiceProvider

将生成的app/Providers/ToastrServiceProvider.php文件移动到我们的packages/yuansir/toastr/src 目录下面,并注册ToastrServiceProvider到config/app.php 的providers 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
'providers' => [

/*
* Laravel Framework Service Providers...
*/
......

/*
* Application Service Providers...
*/
......
Yuansir\Toastr\ToastrServiceProvider::class,
],

6)创建配置文件

新建packages/yuansir/toastr/src/config/toastr.php 来保存toastr.js的options

1
2
3
4
<?php
return [
'options' => []
];

7)创建自定义类

新建Toastr类,来实现toastr 的info,success,error,warning的相关实现,代码还是很简单的,packages/yuansir/toastr/src/Toastr.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
<?php namespace Yuansir\Toastr;
use Illuminate\Session\SessionManager;
use Illuminate\Config\Repository;
class Toastr
{
/**
* @var SessionManager
*/
protected $session;

/**
* @var Repository
*/
protected $config;

/**
* @var array
*/
protected $notifications = [];

/**
* Toastr constructor.
* @param SessionManager $session
* @param Repository $config
*/
public function __construct(SessionManager $session, Repository $config)
{
$this->session = $session;
$this->config = $config;
}

public function render()
{
$notifications = $this->session->get('toastr:notifications');

if(!$notifications) {
return '';
}

foreach ($notifications as $notification) {
$config = $this->config->get('toastr.options');
$javascript = '';
$options = [];
if($config) {
$options = array_merge($config, $notification['options']);
}

if($options) {
$javascript = 'toastr.options = ' . json_encode($options) . ';';
}

$message = str_replace("'", "\\'", $notification['message']);
$title = $notification['title'] ? str_replace("'", "\\'", $notification['title']) : null;
$javascript .= " toastr.{$notification['type']}('$message','$title');";
}

return view('Toastr::toastr', compact('javascript'));
}

/**
* Add notification
* @param $type
* @param $message
* @param null $title
* @param array $options
* @return bool
*/
public function add($type, $message, $title = null, $options = [])
{
$types = ['info', 'warning', 'success', 'error'];
if(!in_array($type, $types)) {
return false;
}

$this->notifications[] = [
'type' => $type,
'title' => $title,
'message' => $message,
'options' => $options
];
$this->session->flash('toastr:notifications', $this->notifications);
}

/**
* Add info notification
* @param $message
* @param null $title
* @param array $options
*/
public function info($message, $title = null, $options = [])
{
$this->add('info', $message, $title, $options);
}

/**
* Add warning notification
* @param $message
* @param null $title
* @param array $options
*/
public function warning($message, $title = null, $options = [])
{
$this->add('warning', $message, $title, $options);
}

/**
* Add success notification
* @param $message
* @param null $title
* @param array $options
*/
public function success($message, $title = null, $options = [])
{
$this->add('success', $message, $title, $options);
}

/**
* Add error notification
* @param $message
* @param null $title
* @param array $options
*/
public function error($message, $title = null, $options = [])
{
$this->add('error', $message, $title, $options);
}

/**
* Clear notifications
*/
public function clear()
{
$this->notifications = [];
}
}

8)创建视图文件

新建 packages/yuansir/toastr/src/views/toastr.blade.php 视图文件:

1
2
3
<link href="http://cdn.bootcss.com/toastr.js/latest/css/toastr.min.css" rel="stylesheet">
<script src="http://cdn.bootcss.com/toastr.js/latest/js/toastr.min.js"></script>
<script type="text/javascript">{!! $javascript !!}</script>

9)创建门面Facade

建立Facade,新建packages/yuansir/toastr/src/Facades/Toastr.php 就是引入了tastr插件,输出我们render方法中的$javascript

1
2
3
4
5
6
7
8
9
<?php namespace Yuansir\Toastr\Facades;
use Illuminate\Support\Facades\Facade;
class Toastr extends Facade
{
protected static function getFacadeAccessor()
{
return 'toastr';
}
}

10)修改服务提供者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php namespace Yuansir\Toastr;

use Illuminate\Support\ServiceProvider;

class ToastrServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
$this->loadViewsFrom(__DIR__ . '/views', 'Toastr');

$this->publishes([
__DIR__.'/views' => base_path('resources/views/vendor/toastr'),
__DIR__.'/config/toastr.php' => config_path('toastr.php'),
]);
}

/**
* Register the application services.
*
* @return void
*/
public function register()
{
$this->app['toastr'] = $this->app->share(function ($app) {
return new Toastr($app['session'], $app['config']);
});
}

/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return ['toastr'];
}
}

$this->loadViewsFrom( DIR . ‘/views’, ‘Toastr’); 就是表示Toastr命名空间的视图文件冲当前目录的views目录中渲染,所以我们上面用 return view(‘Toastr::toastr’, compact(‘javascript’));

$this->publishes 在执行php artisan vendor:publish 时会将对应的目录和文件复制到对应的位置


11)本地测试

修改 config/app.php 添加服务提供者如下:

1
2
3
4
5
6
'aliases' => [
......

'Toastr' => Yuansir\Toastr\Facades\Toastr::class,

],

创建测试控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
php artisan make:controller TestController

<?php

namespace App\Http\Controllers;

use App\Http\Requests;
use Illuminate\Http\Request;
use Toastr;

class HomeController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
//略
}

/**
* Show the application dashboard.
*
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
Toastr::error('你好啊','标题');
dd(session('toastr:notifications'));
return view('home');
}
}

到此结束,大功告成,这样一个Laravel 的 composer 包就开发完成了。

修改命名空间到包的composer.json,因为别人安装这个包的时候不可能也去改项目composer.json的PSR-4的autoload,所以我们把PSR-4的命名空间加到这个包的composer.json中去,修改packages/yuansir/toastr/src/composer.json 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"name": "ryan/toastr-for-laravel",
"description": "toastr.js for laravel5",
"authors": [
{
"name": "Ryan",
"email": "yuansir@live.cn"
}
],
"require": {},
"autoload": {
"psr-4": {
"Yuansir\\Toastr\\": "src/"
}
}
}

12)发布到git上

发布到自己git上:例如Github项目 或者码云: git窗口中命令如下

1
2
3
4
git add .
git status
git commit -m 'vendor'
git push

切记创建版本号:

1
2
$ git tag -a 1.0.0 -m "version 1.0.0"
$ git push --tags

13)发布到packagist上供大家composer安装

提交到Packagist,打开到 packagist.org,登陆后点击右边上角的 submit,并填入git的项目地址git@github.com:yuansir/toastr-for-laravel5.git 点击 check 就OK了


14)别人安装使用

切记翻墙才能加载

1
composer require aaa/bbb (aaa/bbb是你composer.json中的name值)

1
2
3
4
Run composer require ryan/toastr-for-laravel
Add Yuansir\Toastr\ToastrServiceProvider::class, to providers in config/app.php
Add 'Toastr' => Yuansir\Toastr\Facades\Toastr::class, to aliases in config/app.php
Run php artisan vendor:publish


15)Demo视图

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<title>Laravel</title>
</head>
<body>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
{!! Toastr::render() !!}
</body>
</html>


16)Demo

教程的源码和这个包的安装使用方法详见github https://github.com/yuansir/toastr-for-laravel5


[参考资料]
Laravel Composer Package 开发简明教程
Laravel Composer Package 开发简明教程2

laravel 的 名言警句 Inspiring && Collection

首先有几项配置:

config/app.php

1
2
3
4
5
'alias'=>[
...
'Inspiring' => 'Illuminate\Foundation\Inspiring',
...
]

这个告知 laravel 的使用者, Inspiring的方法是可以在模版中或者框架中直接调用的. 所以框架中的示例文件会 Inspiring::quote()这样调用.

这里边的几个句子是这样的:

1
2
3
4
5
6
7
8
'When there is no desire, all things are at peace. - Laozi',           # 无知无欲 - 老子
'Simplicity is the ultimate sophistication. - Leonardo da Vinci', # 简约是复杂的最终形式 - 达芬奇
'Simplicity is the essence of happiness. - Cedric Bledsoe', # 生活的终极要义就是简单
'Smile, breathe, and go slowly. - Thich Nhat Hanh', # 微笑, 平和, 慢慢自然
'Simplicity is an acquired taste. - Katharine Gerould', # 简单就是美好的味道
'Well begun is half done. - Aristotle', # 好的开始是成功的一半
'He who is contented is rich. - Laozi', # 知足者富
'Very little is needed to make a happy life. - Marcus Antoninus', # 无欲则幸福

这里用到了 Illuminate\Support\Collection, 这是一个数组/对象的初始化组件, 可以对数组进行过滤, 合并, 查找 , 这里 有 api 文档, 感觉类似于 underscore 的 php 版本 里边的函数列表

[译] Laravel-mix 3.0 中文文档

原文地址: Laravel Mix Docs

ps:这个版本是 3.0 文档, 大部分功能和 4.0 一致

概览

基本示例

larave-mix 是位于 webpack 顶层的一个简洁的配置层,在 80% 的情况下使用 laravel mix 会使操作变的非常简单。尽管 webpack 非常的强大,但大部分人都认为 webpack 的学习成本非常高。但是如果你不必用再担心这些了呢?

看一下基本的 webpack.mix.js 文件,让我们想象一下我们现在只需要编译 javascript(ES6)和 sass 文件:

1
2
3
4
let mix = require('laravel-mix');

mix.sass('src/app.sass', 'dist')
.js('src/app.js', 'dist');

怎么样,简单吗?

  1. 编译 sass 文件, ./src/app.sass./dist/app.css
       2. 打包在 ./src/app.js 的所有 js(包括任何依赖)到 ./dist/app.js

使用这个配置文件,可以在命令行触发 webpack 指令:node_modules/bin/webpack

在开发模式下,并不需要压缩输出文件,如果在执行 webpack 的时候加上环境变量:export NODE_ENV=production && webpack,文件会自动压缩

less ?

但是如果你更喜欢使用 Less 而不是 Sass 呢?没问题,只要把 mix.sass() 换成 mix.less()就 OK 了。

使用 laravel-mix,你会使发现大部分 webpack 任务会变得更又把握

安装

尽管 laravel-mix 对于 laravel 使用来说最优的,但也能被用于其他任何应用。

laravel 项目

laravel 已经包含了你所需要的一切,简易步骤:

  1. 安装 laravel

  2. 运行 npm install

  3. 查看 webpack.mix.js 文件 ,就可以开始使用了.

你可以在命令行运行 npm run watch 来监视你的前段资源改变,然后重新编译。

在项目根目录下并没有 webpack.config.js 配置文件,laravel 默认指向根目录下的配置文件。如果你需要自己配置它,你可以把它拷贝到根目录下,同时修改 package.json 里的 npm 脚本: cp node_modules/laravel-mix/setup/webpack.config.js ./.

独立项目

首先使用 npm 或者 yarn 安装 laravel-mix,然后把示例配置文件复制到项目根目录下

1
2
3
4
mkdir my-app && cd my-app
npm init -y
npm install laravel-mix --save-dev
cp -r node_modules/laravel-mix/setup/webpack.mix.js ./

现在你会有如下的目录结构

1
2
3
node_modules/
package.json
webpack.mix.js

webpack.mix.js 是你在 webpack 上层的配置文件,大部分时间你需要修改的是这个文件

首先看下 webpack.mix.js 文件

1
2
3
4
5
let mix = require('laravel-mix');

mix.js('src/app.js', 'dist')
.sass('src/app.scss', 'dist')
.setPublicPath('dist');

注意源文件的路径,然后创建匹配的目录结构(你也可以改成你喜欢的结构)。现在都准备好了,在命令行运行 node_modules/.bin/webpack 编译所有文件,然后你将会看到:

  • dist/app.css
  • dist/app.js
  • dist/mix-manifest.json(你的 asset 输出文件,稍后讨论)

干得漂亮!现在可以干活了。

NPM Scripts

把下面的 npm 脚本添加到你的 package.json 文件中可以加速你的工作操作.,laravel 安装的时候已经包含了这个东西了

1
2
3
4
5
6
"scripts": {
"dev": "NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"hot": "NODE_ENV=development webpack-dev-server --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"production": "NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
}

laravel 工作流程

我们先回顾一下通用的工作流程以便你能在自己的项目上采用

1 . 安装 laravel

1
laravel new my-app

2 . 安装 Node 依赖

1
npm install

**3 . 配置 webpack.mix.js **

这个文件所有前端资源编译的入口

1
2
3
4
let mix = require('laravel-mix');

mix.js('resources/assets/js/app.js', 'public/js');
mix.sass('resources/assets/sass/app.scss', 'public/css');

默认会启用 JavaScript ES2017 + 模块绑定,就行 sass 编译一样。

4 . 编译

用如下指令编译

1
node_modules/.bin/webpack

也可以使用 package.json 里的 npm 脚本:

1
npm run dev

然后会看到编译好的文件:

  • ./public/js/app.js
  • ./public/css/app.css

监视前端资源更改:

1
npm run watch

laravel 自带了一个 ./resources/assets/js/components/Example.vue 文件,运行完成后会有一个系统通知。

5 . 更新视图

laravel 自带一个欢迎页面,我们可以用这个来做示例,修改一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Laravel</title>

<link rel="stylesheet" href="{{ mix('css/app.css') }}" />
</head>
<body>
<div id="app">
<example></example>
</div>

<script src="{{ mix('js/app.js') }}"></script>
</body>
</html>

刷新页面,干得漂亮!

常见问题

laravel-mix 必须在 laravel 下使用吗?

不,在 laravel 下使用使最好的,但也可以用在别的任何项目

我的代码没有压缩

只有在 node 环境变量为生产环境时才会被压缩,这样会加速编译过程,但在开发过程中是不必要的,下面是在生成环境下运行 webpack 的示例

1
export NODE_ENV=production && webpack --progress --hide-modules

强烈推荐你把下面的 npm 脚本添加到你的 package.json 文件中,注意 laravel 已经包括了这些了

1
2
3
4
5
6
"scripts": {
"dev": "NODE_ENV=development webpack --progress --hide-modules",
"watch": "NODE_ENV=development webpack --watch --progress --hide-modules",
"hot": "NODE_ENV=development webpack-dev-server --inline --hot",
"production": "NODE_ENV=production webpack --progress --hide-modules"
},

我使用的是 VM,webpack 不能检测到我的文件变化

如果你在 VM 下执行 npm run dev,你会发现 webpack 并不能监视到你的文件改变。如果这样的话,有两种方式来解决这个

  1. 配置 webpack 检测文件系统的变化, 注意:检测文件系统是资源密集型操作并且很耗费电池的使用时长.
  2. 转发文件通过使用类似于 vagrant-fsnotify 之类的东西将通知发送给 VM。注意,这是一个 只有 Vagrant 才有的插件。

检测 VM 文件系统变化, 修改一下你的 npm 脚本,使用 --watch-poll--watch 标签,像这样:

1
2
3
"scripts": {
"watch": "NODE_ENV=development webpack --watch --watch-poll",
}

推送文件改动到 VM, 在主机安装 vagrant-fsnotify

1
vagrant plugin install vagrant-fsnotify

现在你可以配置 vargrant 来使用这个插件, 在 Homestead 中, 在你的 Homestead.yaml 文件类似于这样

1
2
3
4
5
6
7
8
folders:
- map: /Users/jeffrey/Code/laravel
to: /home/vagrant/Code/laravel
options:
fsnotify: true
exclude:
- node_modules
- vendor

一旦你的 vagrant 机器启动, 只需要在主机上运行 vagrant fsnotify 把文件的改动推送到 vm 上, 然后在 vm 内部运行 npm run watch 就能够检测到文件的改动了.

如果你还是有问题,去这儿溜达溜达吧

为什么在我的 css 文件里显示图片在 node_modules 里找不到

你可能用的是相对路径,但是在你的 resources/assets/sass/app.css 里并不存在:

1
2
3
body {
background: url('../img/example.jpg');
}

当引用相对路径的时候,会根据当前文件的路径来搜索,同样的,webpack 会首先搜索 `resources/assets/img/example.jpg ,如果找不到,会继续搜索文件位置,包括 node_modules,如果还找不到,就报错:

1
2
3
ERROR  Failed to compile with 1 errors

This dependency was not found in node_modules:

有两个解决办法:

1 . 让 resources/assets/img/example.jpg 存在这个文件.

2 . 编译 css 的时候添加下面的选项,禁用 css 的 url 处理:

1
2
3
mix.sass("resources/assets/sass/app.scss", "public/css").options({
processCssUrls: false,
});

他对老项目特别有用,因为你的文件夹结构已经完全创建好了。

我不想把 mix-manifest.json 文件放在项目根目录下

如果你没有使用 laravel,你的 mix-manifest.json 文件会被放到项目根目录下,如果你不喜欢的话,可以调用 mix.setPublicPath('dist/'),然后 manifest 文件就会被放到 dist 目录下。

怎样使用 webpack 自动加载模块

webpack 使用 ProvidePlugin 插件加载一些需要的模块,常用的一个例子就是加载 jQuery:

1
2
3
4
5
6
7
8
9
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
});

// in a module
$('#item'); // <= just works
jQuery('#item'); // <= just works
// $ is automatically set to the exports of module "jquery"

当 laravel-mix 自动加载模块的时候(像上面说的那样),你如果想禁用(传一个空对象)或者用你自己的模块覆盖它,可以调用 mix.autoload() 方法:

1
2
3
4
mix.autoload({
jquery: ['$', 'window.jQuery', 'jQuery'], // more than one
moment: 'moment' // only one
});

为什么我看到一个 “Vue packages version mismatch”错误

如果, 更新你的依赖, 你有以下编译失败的信息

1
2
3
4
5
6
Module build failed: Error:

Vue packages version mismatch:

* vue@2.5.13
* vue-template-compiler@2.5.15

这意味着你的 vuevue-template-compiler 依赖不同步, 每一个 Vue 的更新, 版本号必须是相同的. 更新来修复这个错误

1
2
3
4
5
npm update vue

// or

npm install vue@2.5.15

排障

我在更新/安装 mix 时候出现错误

不幸的是,你的依赖项可能没有正确安装的原因有无数个。一个常见的根本原因是安装了老版本的 Node(node -v) 和 npm (npm -v)。第一步,访问 http://nodejs.org 并更新它们。

否则,它通常与需要删除的错误锁文件有关。让这一系列命令尝试从头开始安装一切

1
2
3
4
rm -rf node_modules
rm package-lock.json yarn.lock
npm cache clear --force
npm install

为什么 webpack 不能找到我的 app.js 条目文件?

如果你遇到这样的失败信息……

1
2
3
These dependencies were not found:

* /Users/you/Sites/folder/resources/assets/js/app.js

… 你可能使用 npm 5.2 (npm -v) 这个版本。这个版本引入了一个导致安装错误的错误。该问题已被在 npm 5.3 修复。请升级,然后重新安装

1
2
3
4
rm -rf node_modules
rm package-lock.json yarn.lock
npm cache clear --force
npm install

API

Javascript

1
mix.js(src|[src], output)

简单的一行代码,larave mix 可以执行很多重要的操作

  • ES2017+ 模块编译
  • 创建并且编译 vue 组件(通过 vue-loader)
  • 模块热替换(HMR)
  • Tree-shaking 打包技术,webpack2 里新增的(移除无用的库)
  • 提取和拆分 vendor 库(通过mix.extract()方法), 使长期缓存变的容易
  • 自动版本化(文件哈希),通过 mix.version()

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let mix = require('laravel-mix');

// 1. A single src and output path.
mix.js('src/app.js', 'dist/app.js');


// 2. For additional src files that should be
// bundled together:
mix.js([
'src/app.js',
'src/another.js'
], 'dist/app.js');


// 3. For multiple entry/output points:
mix.js('src/app.js', 'dist/')
.js('src/forum.js', 'dist/');

laravel 示例

考虑到典型的 laravel 默认安装的时候会把入口定位在 ./resources/assets/js/app.js,所以我们先准备一个 webpack.mix.jsapp.js 编译到 ./public/js/app.js

1
2
3
let mix = require('laravel-mix');

mix.js('resources/assets/js/app.js', 'public/js');

现在上面所有的项你都可以用了,只需要调用一个方法。

在命令行调用 npm run dev 执行编译。

Vue 组件

laravel mix 包罗万象,支持 vue 组件编译,如果你不使用 vue 的话,可以忽略这块。

单文件组件是 vue 最重要的特征。在一个文件里为一个组件定义模板,脚本,样式表。

./resources/assets/js/app.js

1
2
3
4
5
6
7
import Vue from "vue";
import Notification from "./components/Notification.vue";

new Vue({
el: "#app",
components: { Notification },
});

在上面,我们导入了 vue(首先你需要执行npm install vue --save-dev安装 vue),然后引入了一个叫 Notification 的 vue 组件并且注册了 root vue 实例。

./resources/asset/js/components/Notification.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="notification">
{{ body }}
</div>
</template>

<script>
export default {
data() {
return {
body: 'I am a notification.'
}
}
}
</script>
<style>
.notification {
background: grey;
}
</style>

如果你了解 vue,这些你都会很熟悉,继续。

./webpack.mix.js

1
2
3
let mix = require('laravel-mix');

mix.js('resources/assets/js/app.js', 'public/js');

执行 npm run dev 编译文件,这样就简单的创建了一个 HTML 文件,引入 ./js/app.js 文件,然后在浏览器里查看吧!

React 支持

laravel mix 也装载了基本的 react 支持,只要把 mix.js()改成 mix.react()并且使用相同的参数。在底层,mix 会引用 react 需要的任何 babel 插件。

1
mix.react('resources/assets/js/app.jsx', 'public/js/app.js');

当然,你仍然需要使用 npm 安装 react 和 reactDOM,不过要注意小心行事。

库代码分离

1
2
mix.js(src, output)
.extract(['any', 'vendor', 'library']);

把所有的 js 都打包成一个文件会伴随着潜在的风险:每次更新项目中就算很小的一部分都需要破坏所有用户的缓存,这意味着你的第三方库需要重新被下载和缓存。这样很不好。

一个解决的办法是分离或者提取你的库文件。

  • 应用代码: app.js
  • vendor 库: vendor.js
  • Manifest(webpack runtime): manifest.js
1
mix.extract(['vue', 'jquery']);

extract 方法接受一个你想要从打包文件里提取出的库的数组,使用这个方法,Vue 和 jQuery 的源代码都会被放在 vendor.js 里。如果在未来你需要对应用代码做一些微小的变动,并不会对大的 vendor 库产生影响,它们依然会留在长期缓存。

一旦执行 webpack 打包文件,你会发现三个新的文件,你可以在 HTML 页面引用它们。

1
2
3
<script src="/js/manifest.js"></script>
<script src="/js/vendor.js"></script>
<script src="/js/app.js"></script>

实际上,我们付出了一些 HTTP 请求的代价(就是会多请求几次)来换取长期缓存的提升。

Manifest 文件是什么

webpack 编译的时候会有一些 run-time 代码协助其工作。如果没有使用 mix.extract(),这些代码你是看不到的,它会在打包文件里,然而,如果我们分离了代码并且允许长期缓存,在某些地方就需要这些 run-time 代码,所以,mix 会把它提取出来,这样一来,vendor 库和 manifest 文件都会被缓存很长时间。

浏览器自动刷新

1
mix.browserSync('my-site.test');

BrowserSync 能自动监控文件变动并且把你的变化通知浏览器, – 完全不需要手动刷新。你可以调用 mix.browserSync() 方法来开启这个功能:

1
2
3
4
5
6
7
8
mix.browserSync('my-domain.test');

// Or:

// https://browsersync.io/docs/options/
mix.browserSync({
proxy: 'my-domain.test'
})

参数可以传字符串(proxy),也可以传对象(BrowserSync 设置)。你声明的域名作为 proxy 是非常重要的,Browsersync 将通过代理网址来输出到你的虚拟主机(webpack dev server).

其他选项可以从 Browsersync Documentation

现在, 启动 dev server (npm run watch), 并进行下一步操作吧.

模块热替换

laravel mix 对模块热替换提供了无缝的支持。

模块热替换(或者叫热加载),意思就是当 javascript 改变刷新页面的时候可以维持组件的状态,例如现在有一个计数器,按一下按钮,计数器会加 1,想象一下你点了很多次然后修改一下组件的相关文件,浏览器会实时的反映出你所做出的更改而保持计数器不变,计数器不会被重置,这就是热加载的意义最在。

在 laravel 里的用法

Laravel 和 Laravel 一起工作, 来抽象出热加载的复杂性.

看一下 laravel 里的 package.json 文件,在 scripts 模块,你可以看到:

1
2
3
4
5
6
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --progress --hide-modules",
"watch": "cross-env NODE_ENV=development webpack --watch --progress --hide-modules",
"hot": "cross-env NODE_ENV=development webpack-dev-server --inline --hot",
"production": "cross-env NODE_ENV=production webpack --progress --hide-modules"
}

注意一下 hot 选项,这个地方就是你所需要的,在命令行执行 npm run hot 会启动一个 node 服务器并且监视你的 bundle 文件,接下来,在浏览器打开你的 laravel 应用,一般应该是 http://my-app.test

在 laravel 应用里使用热加载很重要的一点是要保证所有的脚本资源引用的是前面启动的 node 服务器的 url:http://localhost:8080,现在你可以手动更新你的 HTML\Blade 文件了:

1
2
3
4
<body>
<div id="app">...</div>
<script src="http://localhost:8080/js/bundle.js"></script>
</body>

假设你有一些组件,尝试在浏览器里更改他们的状态,然后更新他们的模板文件,你可以看到浏览器会立刻反应出你的更新,但是状态并没有被改变。

但是,在开发部署环境下手动更新 url 会是一个负担,所以,laravel 提供了一个 mix()方法,他会动态的构建 js 或者样式表的引用,然后输出。上面的代码因此可以修改成:

1
2
3
4
5
<body>
<div id="app"></div>

<script src="{{ mix('js/bundle.js') }}"></script>
</body>

调整之后,Laravel 将为你做这项工作。如果运行 npm run hot 以启用热重加载,则该函数将设置必要的 http://localhost:8080 作为 URL。相反,如果您使用 npm run devnpm run pro,它将使用域名作为基准 url。

在 Https 中使用
如果你在 HTTPS 连接上开发你的应用,你的热重加载脚本和样式也必须通过 HTTPS 服务。要实现这一点,可以将 -—https 标志添加到 package.json 中的热选项命令中。

1
2
3
"scripts": {
"hot": "NODE_ENV=development webpack-dev-server --inline --hot --https",
}

通过上面的设置,webpack-dev-server 将会生成一个自签名证书。如果你希望使用自己的证书,可以使用以下设置:

1
"hot": "NODE_ENV=development webpack-dev-server --inline --hot --https --key /path/to/server.key --cert /path/to/server.crt --cacert /path/to/ca.pem",

现在在你的 Html/Blade 文件中可以使用

1
<script src="https://localhost:8080/js/bundle.js"></script>

或者

1
<script src="{{ mix('js/bundle.js') }}"></script>

在 spa 里的用法

laravel mix 包含了流行的 vue-loader 包,这意味着,如果是单页应用,你什么都不需要做,它是开箱即用的。

版本化

1
2
mix.js('src', 'output')
.version([]);

为了帮助长期缓存,Laravel Mix 提供了 mix.version() 方法,它支持文件散列。比如app.js?id=8e5c48eadbfdd5458ec6。这对清除缓存很有用。假设你的服务器自动缓存了一年的脚本,以提高性能。这很好,但是,每当您对应用程序代码进行更改时,需要一些方法来告诉用户更新缓存, 这通常是通过使用查询字符串或文件哈希来完成的。

启用了版本控制之后,每次代码更改时,都会生成一个新的散列查询字符串文件。看以下webpack.mix.js 文件

编译后,你会在 mix-manifest.json 文件看到 /css/app.css?id=5ee7141a759a5fb7377a/js/app.js?id=0441ad4f65d54589aea5。当然,你的特定散列将是唯一的。每当你调整 JavaScript 时,编译后的文件将会收到一个新的散列名称,这将有效地破坏缓存,一旦被推到生产环境中。

举个例子,试试 webpack --watch,然后修改一下你的 JavaScript。你将立即看到一个新生成的打包文件和样式表。

导入版本文件

这就引出了一个问题:如果名称不断变化,我们如何将这些版本化的脚本和样式表包含到 HTML 中呢?是的,这很棘手。答案将取决于你构建的应用程序的类型。对于 SPA,你可以动态地读取 Laravel Mix 生成的 manifest.json 文件,提取资料文件名(这些名称将被更新,以反映新的版本文件),然后生成 HTML。

Laravel 用户

对于 Laravel 项目,一个解决方案是开箱即用的。只需调用全局 mix() 函数,就完成了!我们将计算出导入的适当文件名。这里有一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>App</title>
<link rel="stylesheet" href="{{ mix('css/app.css') }}" />
</head>
<body>
<div id="app">
<h1>Hello World</h1>
</div>

<script src="{{ mix('js/app.js') }}"></script>
</body>
</html>

将未散列的文件路径传递给 mix() 函数,然后在后端,我们将弄清楚应该导入哪个脚本或样式表。请注意,你可能/应该使用这个函数,即使没有对文件进行版本控制。

版本化附加文件

mix.version() 将自动生成编译后的 JavaScript、Sass/Less 或合并文件。但是,如果你还希望将额外的文件作为构建的一部分,简单地传递路径或路径数组,就像这样:

1
mix.version(['public/js/random.js']);

现在,我们会版本化任何相关的编译文件,但是我们还会附加一个查询字符串,public/js/random.js?{hash},并更新 mix-manifest.json 文件。

Css 预处理器

1
2
3
4
5
mix.sass('src', 'output', pluginOptions);
mix.standaloneSass('src', 'output', pluginOptions); // Isolated from Webpack build.
mix.less('src', 'output', pluginOptions);
mix.stylus('src', 'output', pluginOptions);
mix.postCss('src', 'output', [ require('precss')() ])

一个单一的方法调用允许你编译你的 Sass,Less,或 Stylus 文件,同时自动应用 CSS3 前缀。

虽然 webpack 可以将所有 CSS 直接插入到绑定的 JavaScript 中,但 Laravel Mix 会自动执行必要的步骤,将其提取到你想要的输出路径中。

多构建

如果你需要编译多个顶级文件,你可以根据需要调用 mix.sass()(或任何一个预处理器变体). 对于每个调用,webpack 将输出一个包含相关内容的新文件。

1
2
mix.sass('src/app.scss', 'dist/') // creates 'dist/app.css'
.sass('src/forum.scss', 'dist/'); // creates 'dist/forum.css'

例子

让我们看一个例子

webpack.mix.js

1
2
3
let mix = require('laravel-mix');

mix.sass('resources/assets/sass/app.sass', 'public/css');

./resources/assets/sass/app.sass

1
2
3
4
$primary: grey

.app
background: $primary

Tip : 对于 Sass 编译, 你可以使用 .sass.scss 语法

像往常一样运行 npm run webpack 进行编译. 你会发现 ./public/css/app.css 文件包含

1
2
3
.app {
background: grey;
}

插件选项

编译的时候, Laravel Mix 的首先是去分别的调用 Node-Sass, Less,和 Slylus 来编译你的 Sass, Less 文件。有时,你可能需要重写我们传递给它们的默认选项。可以将它们作为第三个参数提供给 mix.sass(), mix.less()mix.stylus()

Stylus 插件
如果使用 Stylus, 你可能希望安装额外的插件,比如 Rupture 。运行 npm install rupture 来安装这个插件,然后在你的 mix.stylus() 中调用, 例如:

1
2
3
4
5
mix.stylus('resources/assets/stylus/app.styl', 'public/css', {
use: [
require('rupture')()
]
});

如果希望更深一步,并且在全局中自动导入插件,您可以使用 import 选项。这里有一个例子:

1
2
3
4
5
6
7
8
9
10
11
mix.stylus('resources/assets/stylus/app.styl', 'public/css', {
use: [
require('rupture')(),
require('nib')(),
require('jeet')()
],
import: [
'~nib/index.styl',
'~jeet/jeet.styl'
]
});

就是这个样子滴!

CSS url() 重写

一个关键的 webpack 概念是,它将重写你的样式表中的任何 url()。虽然这可能最初听起来很奇怪,但它是一项非常强大的功能。

一个例子

假设我们想要编译一些 Sass,其中包含一个图像的相对 url。

1
2
3
.example {
background: url("../images/thing.png");
}

提示:url()的绝对路径将被排除在 url 重写之外。因此,url('/images/thing.png')url('http://example.com/images/thing.png') 将不会被更改。

注意,这里说的是相对 URL? 默认情况下,Laravel Mix 和 webpack 将会找到 thing.png ,将其复制到 public/images 文件夹中,然后在生成的样式表中重写 url()。因此,编译的 CSS 将是:

1
2
3
.example {
background: url(/images/thing.png?d41d8cd98f00b204e9800998ecf8427e);
}

这也是 webpack 的一个很酷的特性。然而,它确实有一种倾向,让那些不理解 webpack 和 css-loader 插件如何工作的人感到困惑。你的文件夹结构可能已经是您想要的了,而且你希望不要修改那些url()。如果是这样的话,我们确实提供了一个覆盖方式:

1
2
3
4
mix.sass('src/app.scss', 'dist/')
.options({
processCssUrls: false
});

把这个加上你的 webpack.mix.js 文件中,我们将不再匹配 url() 或复制资源到你的公共目录。因此,编译后的 CSS 将与你输入时一样:

1
2
3
.example {
background: url("../images/thing.png");
}

这样的好处是,当禁用 url 处理时,您的 Webpack, Sass 编译和提取可以更快地编译。

PostCSS 插件

默认情况下,Mix 将通过流行的 Autoprefixer PostCSS plugin 将所有的 CSS 文件连接起来。因此,你可以自由使用最新的 CSS 3 语法,并理解我们将自动应用任何必要的浏览器前缀。在大多数情况下,默认设置应该就可以,但是,如果你需要调整底层的自动修复程序配置,那么如下所示:

1
2
3
4
5
6
7
8
9
10
mix.sass('resources/assets/sass/app.scss', 'public/css')
.options({
autoprefixer: {
options: {
browsers: [
'last 6 versions',
]
}
}
});

另外,如果你想完全禁用它——或者依赖一个已经包含 自动前缀的 PostCSS 插件:

1
2
mix.sass('resources/assets/sass/app.scss', 'public/css')
.options({ autoprefixer: false });

但是,你可能想要在构建中应用额外的 PostCSS 插件。木问题, 只需在 NPM 中安装所需的插件,然后在 webpack.mix.js 文件中引用,如下所示:

1
2
3
4
5
6
mix.sass('resources/assets/sass/app.scss', 'public/css')
.options({
postCss: [
require("postcss-custom-properties")
]
});

完成了!现在可以使用和编译自定义 CSS 属性(如果这是您的东西)。例如,如果 resources/assets/sass/app.scss 包含…

1
2
3
4
5
6
7
:root {
--some-color: red;
}

.example {
color: var(--some-color);
}

编译完成将会是

1
2
3
.example {
color: red;
}

PostCss 不适用 Sass/Less

或者,如果你更喜欢跳过 Sass/Less/Stylus 编译步骤,而是使用 PostCSS,你可以通过mix.postCss() 方法来完成。

1
2
3
mix.postCss('resources/assets/css/main.css', 'public/css', [
require('precss')()
]);

请注意,第三个参数是应该应用于你构建的 postcss 插件的数组。

独立 Sass 构建

如果你不希望 Mix 和 Webpack 以任何方式处理你的 Sass 文件,你可以使用mix.standaloneSass(),这将大大改善你应用程序的构建时间。请记住:如果你选择了这条路线,Webpack 不会触及你的 CSS。它不会重写 url,复制资源(通过 file-loader),或者应用自动图像优化或 CSS 清理。如果这些特性对于你的应用程序来说是没有必要的,那么一定要使用这个选项而不是mix.sass()

1
mix.standaloneSass('resources/assets/sass/app.scss', 'public/css');

注意:如果你正在使用 standaloneSass,在使用 npm run watch 进行文件更改时,你将需要使用下划线来前缀导入的文件,以将它们标记为依赖文件(例如,_header.scss _alert.scss)。如果不这样做,将导致 Sass 编译错误和/或额外的 CSS 文件。

文件复制

1
2
3
4
mix.copy(from, to);
mix.copy('from/regex/**/*.txt', to);
mix.copy([path1, path2], to);
mix.copyDirectory(fromDir, toDir);

有时, 你需要复制一个或者多个文件作为构建过程的一部分。没有问题, 这是小事一桩。使用mix.copy() 方法指定源文件或文件夹,然后指定您想要的目标文件夹/文件

1
mix.copy('node_modules/vendor/acme.txt', 'public/js/acme.txt');

在编译时,’acme’ 文件将被复制到 ‘public/js/acme.txt’。这是一个常见的用例,当你希望将一组字体通过 NPM 安装到 public 目录时。

系统通知

默认情况下,Laravel Mix 将显示每个编译的系统通知。这样,你就可以快速查看是否有需要查询的错误。但是,在某些情况下,这是不可取的(例如在生产服务器上编译)。如果发生这种情况,它们可以从你的 webpack.mix.js 文件中禁用。

1
2
mix.js(src, output)
.disableNotifications();

文件组合和最小化

1
2
3
4
mix.combine(['src', 'files'], 'destination');
mix.babel(['src', 'files'], destination);
mix.minify('src');
mix.minify(['src']);

如果使用得当,Laravel Mix 和 webpack 应该负责所有必要的模块捆绑和最小化。但是,你可能有一些遗留代码或第三方库需要连接和最小化, 这并不是一个问题。

组合文件

考虑下面的代码片段:

1
mix.combine(['one.js', 'two.js'], 'merged.js');

这自然会合并 one.jstwo.js 到一个单独的文件,叫做 merged.js。与往常一样,在开发期间,合并文件将保持未压缩状态。但是,对于生产(export NODE_ENV=production),这个命令将会最小化 merged.js

组合文件与 Babel 编译。

如果需要组合 使用 ES2015 方法编写的 JavaScript 文件,你可以更新的 mix.combine() 调用 mix.babel()。方法签名相同。唯一的区别是,在将文件组合起来之后,Laravel Mix 将对结果进行 Babel 编译,从而将代码转换成所有浏览器都能理解的 JavaScript 代码。

1
mix.babel(['one.js', 'two.js'], 'merged.js');

最小化文件

同样,你也可以使用 mix.minify() 命令缩小一个或多个文件。

1
2
mix.minify('path/to/file.js');
mix.minify(['this/one.js', 'and/this/one.js']);

这里有一些值得注意的事情:

  • 该方法将创建一个额外的 *.min.ext 文件。因此,压缩 app.js 将生成 app.min.js
  • 再一次声明,压缩只会在生产过程中发生。(export NODE_ENV=production)。
  • 不需要调用 mix.combine(['one.js', 'two.js'], 'merged.js').minify('merged.js'); ,只使用单一的 mix.combine() 调用。它会兼顾两者。

    重要:请注意,压缩只适用于 CSS 和 JavaScript 文件。minifier 不理解任何其他提供的文件类型。

自动加载

1
2
3
mix.autoload({
jquery: ['$', 'window.jQuery']
});

Webpack 提供了必要的功能,可以在 Webpack 所要求的每个模块中把一个模块作为变量。如果你使用的是一个特定的插件或库,它依赖于一个全局变量,例如 jQuery, mix.autoload() 可能会对你有用。

考虑下面的例子:

1
2
3
mix.autoload({
jquery: ['$', 'window.jQuery']
});

该代码片段指定 webpack 应该将 var $ = require('jquery') 添加到它所遇到的全局$标识符或 window.jQuery 中。漂亮!

事件钩子

1
mix.then(function () {});

你可能需要监听每次 webpack 完成编译的事件。也许你需要手动应用一些适合你的应用程序的逻辑。如果是这样,您可以使用 mix.then() 方法来注册任何回调函数。这里有一个例子:

1
2
3
4
mix.js('resources/assets/js/app.js', 'public/js')
.then(() => {
console.log('webpack has finished building!');
});

回调函数将通过 webpack Stats 对象,允许对所执行的编译进行检查:

1
2
3
4
5
mix.js('resources/assets/js/app.js', 'public/js')
.then((stats) => {
// array of all asset paths output by webpack
console.log(Object.keys(stats.compilation.assets));
});

可以在这里找到 Stats 对象的官方文档 : https://github.com/webpack/docs/wiki/node.js-api#stats

快速 webpack 配置

1
mix.webpackConfig({} || cb);

当然,你可以自由编辑提供的 webpack.config.js 文件,在某些设置中,更容易直接从你的 webpack.mix.js 修改或覆盖默认设置。对于 Laravel 应用来说尤其如此,默认情况下是在项目根文件夹中没有 webpack.config.js

例如,你可能希望添加一个由 webpack 自动加载的模块的自定义数组。在这个场景中,您有两个选项:

  • 根据需要编辑你的 webpack.config.js 文件
  • 在你的 webpack.mix.js 中调用 mix.webpackConfig() 文件,并传递重写参数。然后混合将进行一个深度合并。
    下面,作为一个示例,我们将为 Laravel Spark 添加一个自定义模块路径。
1
2
3
4
5
6
7
8
mix.webpackConfig({
resolve: {
modules: [
'node_modules',
path.resolve(__dirname, 'vendor/laravel/spark/resources/assets/js')
]
}
});

使用回调函数

当传递回调函数时,你可以访问 webpack 及其所有属性。

1
2
3
4
5
6
7
8
9
10
11
mix.webpackConfig(webpack => {
return {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
})
]
};
});

扩展 Mix

基于组件的系统 Mix 使用场景构建它的 API,你也可以访问—是否为你的项目扩展 Mix,或者作为一个可重用的包分发到世界。

例子

1
2
3
4
5
6
7
8
9
// webpack.mix.js;
let mix = require('laravel-mix');

mix.extend('foo', function(webpackConfig, ...args) {
console.log(webpackConfig); // the compiled webpack configuration object.
console.log(args); // the values passed to mix.foo(); - ['some-value']
});

mix.js('src', 'output').foo('some-value');

在上面的示例中,我们可以看到 mix.extend() 接受两个参数:在定义组件时应该使用的名称,以及一个回调函数或类,这些函数注册并组织必要的 webpack 逻辑。在后台,一旦构建了底层 webpack 配置对象,Mix 将触发这个回调函数。这将给你一个机会来插入或覆盖任何必要的设置。

虽然简单的回调函数可能对快速扩展很有用,但在大多数情况下,您可能希望构建一个完整的组件类,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mix.extend(
'foo',
new class {
register(val) {
console.log('mix.foo() was called with ' + val);
}

dependencies() {}

webpackRules() {}

webpackPlugins() {}

// ...
}()
);

在扩展 Mix 时,通常需要触发一些指令:

  • 安装这些依赖关系。
  • 将此规则/加载程序添加到 webpack 中。
  • 包含这个 webpack 插件。
  • 完全覆盖 webpack 配置的这一部分。
  • 将此配置添加到 Babel。
  • 等。

这些操作中的任何一个都是带有 Mix 组件系统有联系。

组件的接口

  • name:当调用组件时,应该使用什么作为方法名。(默认为类名。)
  • dependencies:列出应该由 Mix 安装的所有 npm 依赖项。
  • register:当您的组件被调用时,所有的用户参数将立即被传递给这个方法。
  • boot:启动组件。这个方法是在用户的 webpack.mix 之后触发的。js 文件已经加载完毕。
  • webpackEntry:附加到主混合 webpack 入口对象。
  • webpackRules:与主 webpack 加载器合并的规则。
  • webpackplugin:与主 webpack 配置合并的插件。
  • webpackConfig:覆盖生成的 webpack 配置。
  • babelConfig:额外的 Babel 配置应该与 Mix 的默认值合并。

这里有一个示例/虚拟组件,它可以让你更好地了解如何构建自己的组件。更多的例子,请参考在后台 Mix 使用的组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
class Example {
/**
* The optional name to be used when called by Mix.
* Defaults to the class name, lowercased.
*
* Ex: mix.example();
*
* @return {String|Array}
*/
name() {
// Example:
// return 'example';
// return ['example', 'alias'];
}

/**
* All dependencies that should be installed by Mix.
*
* @return {Array}
*/
dependencies() {
// Example:
// return ['typeScript', 'ts'];
}

/**
* Register the component.
*
* When your component is called, all user parameters
* will be passed to this method.
*
* Ex: register(src, output) {}
* Ex: mix.yourPlugin('src/path', 'output/path');
*
* @param {*} ...params
* @return {void}
*
*/
register() {
// Example:
// this.config = { proxy: arg };
}

/**
* Boot the component. This method is triggered after the
* user's webpack.mix.js file has executed.
*/
boot() {
// Example:
// if (Config.options.foo) {}
}

/**
* Append to the master Mix webpack entry object.
*
* @param {Entry} entry
* @return {void}
*/
webpackEntry(entry) {
// Example:
// entry.add('foo', 'bar');
}

/**
* Rules to be merged with the master webpack loaders.
*
* @return {Array|Object}
*/
webpackRules() {
// Example:
// return {
// test: /\.less$/,
// loaders: ['...']
// });
}

/*
* Plugins to be merged with the master webpack config.
*
* @return {Array|Object}
*/
webpackPlugins() {
// Example:
// return new webpack.ProvidePlugin(this.aliases);
}

/**
* Override the generated webpack configuration.
*
* @param {Object} webpackConfig
* @return {void}
*/
webpackConfig(webpackConfig) {
// Example:
// webpackConfig.resolve.extensions.push('.ts', '.tsx');
}

/**
* Babel config to be merged with Mix's defaults.
*
* @return {Object}
*/
babelConfig() {
// Example:
// return { presets: ['react'] };
}

请注意,上面示例中的每个方法都是可选的。在某些情况下,您的组件可能只需要添加一个 webpack 加载程序和/或调整混合使用的 Babel 配置。没有问题的话省略其余的接口。

1
2
3
4
5
6
7
8
class Example {
webpackRules() {
return {
test: /\.test$/,
loaders: []
};
}
}

现在,当 Mix 构造底层 webpack 配置时,你的规则将包含在生成的webpackConfig.module.rules 数组中。

使用

一旦你构建或安装了想要的组件,只需从 webpack.mix.js 中获取它即可,你都准备好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// foo-component.js

let mix = require('laravel-mix');

class Example {
webpackRules() {
return {
test: /\.test$/,
loaders: []
};
}
}

mix.extend('foo', new Example());
1
2
3
4
5
6
7
8
9
// webpack.mix.js

let mix = require('laravel-mix');
require('./foo-component');

mix
.js('src', 'output')
.sass('src', 'output')
.foo();

自定义方法

LiveReload

当 Laravel Mix 与 Browsersync 已经支持了开箱即用,你可能更喜欢使用 LiveReload, 当检测到修改时,LiveReload 可以自动监视您的文件并刷新页面。

安装 webpack-livereload-plugin

1
npm install webpack-livereload-plugin@1 --save-dev

配置 webpack.mix.js

将以下几行添加到 webpack.mix.js 底部。

1
2
3
4
5
6
7
var LiveReloadPlugin = require('webpack-livereload-plugin');

mix.webpackConfig({
plugins: [
new LiveReloadPlugin()
]
});

虽然 LiveReload 有她很好用的默认值,但是这里可以查看一个可用的插件选项列表

安装 LiveReload.js

最后,我们需要安装 LiveReload.js。您可以通过 LiveReload Chrome 插件,或者在你的主要站点模板的关闭 </body> 标记之前添加以下代码:

1
2
3
@if(config('app.env') == 'local')
<script src="http://localhost:35729/livereload.js"></script>
@endif

运行 dev server

1
npm run watch

现在,LiveReload 将自动监控您的文件并在必要时刷新页面。享受吧!

Jquery UI

jQuery UI 是一个用于呈现公共组件的工具包,比如 datepickers、draggables 等。不需要做任何调整,以使其与 Laravel Mix 一起工作。

构建 webpack.mix.js 配置

1
2
mix.js('resources/assets/js/app.js', 'public/js')
.sass('resources/assets/sass/app.scss', 'public/css');

安装 jquery-ui

1
npm install jquery-ui --save-dev

加载必要插件

1
2
3
4
5
6
// resources/assets/js/app.js

import $ from 'jquery';
window.$ = window.jQuery = $;

import 'jquery-ui/ui/widgets/datepicker.js';

加载 CSS

1
2
3
// resources/assets/sass/app.scss

@import '~jquery-ui/themes/base/all.css';

触发 UI 组件

1
2
// resources/assets/js/app.js
$('#datepicker').datepicker();

高级配置

Laravel Mix 配置项

1
2
3
4
5
6
7
8
9
10
mix.options({
extractVueStyles: false,
processCssUrls: true,
uglify: {},
purifyCss: false,
//purifyCss: {},
postCss: [require('autoprefixer')],
clearConsole: false
});

如果需要的话可以使用一些混合选项和覆盖选项。请注意上面的选项,以及它们的默认值。这里有一个快速概述:

  • extractVueStyles:提取 .vue 组件样式(CSS 在<style>标签内)到一个专用文件,而不是将其嵌入到 HTML 中。
  • globalVueStyles:表示一个文件包含在每个组件样式中。这个文件应该只包含变量、函数或 mixin,以便在最终的编译文件中防止重复的 css。这个选项只有在启用了提取工具时才有效。
  • processCssUrls:进程/优化相对样式表url()。默认情况下,Webpack 会自动更新这些 url。但是,如果您的文件夹结构已经按照您想要的方式进行组织,那么将此选项设置为 false 以禁用处理。
  • uglify:使用这个选项来合并你的项目需要的任何定制的 uglify 选项。
  • purifyCss:如果你想要混合自动读取你的 HTML/Blade 文件,并删除你的 CSS 包,你可以将这个选项设置为 true。您还可以传递包含 purifycss-webpack 选项的对象。
  • postCss:合并任何自定义的 postCss 插件。
  • clearConsole:设置为 false,如果您不想在每次构建后清除终端/控制台。

[译] 使用 Sami 生成 PHP 文档

原文地址: Generating PHP Documentation with Sami

为方法, 类, 函数生成文档已经成为了程序员的习惯, 所以需要知道通过源代码生成独立的文档. 本文中我会介绍 Sami, 一款 新的 API 文档生成工具.

什么是 DocBlock?

DocBlock 是插入到 类, 接口, 方法, 属性顶部的多行注释, 为了阐明这个, 我们看下 Laravel 中的代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
abstract class Manager
{
/**
* The application instance.
*
* @var \Illuminate\Foundation\Application
*/
protected $app;


/**
* Create a new manager instance.
*
* @param \Illuminate\Foundation\Application $app
* @return void
*/
public function __construct($app)
{
$this->app = $app;
}
}

DocBlock   开始于  /**, 结束于 */, 每行之间使用 *.   当定义一个类属性或者方法的时候, 我们会写一个描述或者多个注释来描述这个功能. 在这些示例中 @param@var 会被用到. 你可以访问 phpDocumentor 官方网站来熟悉每个标签的用法.

API 文档生成器

世界上充满许多文档生成器, 但是最好的一个是 phpDocumentor, 我喜欢 Sami   的主要原因是能够使用 github 上版本控制的文档, 并且可以使用  Twig 来生成模版

安装 Sami

有两种方式来安装 Sami. 第一个是 下载 Phar 压缩包并且使用 php 来运行

1
php sami.phar

第二个方式是通过  Composer. 你可以运行 composer require sami/sami:3.0.* 命令来添加 sami 包到你的项目中.

1
php vendor/sami/sami/sami.php

复制 Laravel’s 文档

本示例中, 我会生成  Laravel Illuminate package 的文档

1
git clone git@github.com:laravel/framework.git docs

现在我们的文件结构如下所示:

1
2
3
docs/
vendor/
composer.json

update 命令用来更新文档, 下面是使用方法:

1
php vendor/sami/sami/sami.php update config/config.php

config.php 文件来描述你的文档结构并且告知如何渲染输出.

Configuration / 配置

配置文件必须返回 Sami\Sami 实例, 他接受 Symfony\Component\Finder\Finder   实例和一系列的选择项.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// config/config.php

$dir = __DIR__ . '/../docs';

$iterator = Symfony\Component\Finder\Finder::create()
->files()
->name('*.php')
->exclude('build')
->exclude('tests')
->in($dir);

$options = [
'theme' => 'default',
'title' => 'Laravel API Documentation',
'build_dir' => __DIR__ . '/../build/laravel',
'cache_dir' => __DIR__ . '/../cache/laravel',
];

$sami = new Sami\Sami($iterator, $options);

return $sami;

$dir 变量保存源代码的路径.  $iterator 获取所有文件并且选择 *.php 并且排除 buildtest 目录. $options 变量里边的内容比较显而易见. theme设置成 default , 我们稍后讲如何建立自己的主题. build_dir 保存输出文件. cache_dir 放置 Twig 缓存, title 是生成文档的标题

现在, 打开命令行并运行如下命令

1
php vendor/sami/sami/sami.php update config/config.php

命令执行完之后, 你可以运行内置的 PHP 服务器来查看文档, 运行 php -S localhost:8000 -t build/, 并且访问  http://localhost:8000/laravel/ 来查看运行结果

使用 Git 版本控制

我提到了使用 Sami 的一个重要原因就是他的版本控制. options['versions'] 参数接受一个  Sami\Version\GitVersionCollection 实例来保存你的 Git 库配置. 如下, 让我们创建  5.04.2   分支的文档.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$dir = __DIR__ . '/../docs/src';

$iterator = Symfony\Component\Finder\Finder::create()
->files()
->name('*.php')
->in($dir);

$versions = Sami\Version\GitVersionCollection::create($dir)
->add('5.0', 'Master')
->add('4.2', '4.2');


$options = [
'theme' => 'default',
'versions' => $versions,
'title' => 'Laravel API Documentation',
'build_dir' => __DIR__ . '/../build/laravel/%version%',
'cache_dir' => __DIR__ . '/../cache/laravel/%version%',
];

$sami = new Sami\Sami($iterator, $options);

return $sami;

当使用版本的时候, 你的 build_dircache_dir 必须包含 %version% 标签, add 方法的第二个参数是显示在下拉选项中的标签. 你可以使用 addFromTags, setFilter 方法来过滤版本号

1
php vendor/sami/sami/sami.php update config/config.php

现在你生成的文档目录将会包含每个版本的说明文档. 用户也能够选择去阅读他想要的版本.

创建主题

现在, 我们仅仅使用了 default 主题, 但是 Sami 是可以扩展的, 让我们能够创建自己的主题.

这个  vendor/sami/sami/Sami/Resources/themes 文件夹存储了默认主题. 然而你并不能够把你的主题文件放到这里. Sami 提供了一个方法来添加自定义主题

1
2
3
4
5
6
// config/config.php

$templates = $sami['template_dirs'];
$templates[] = __DIR__ . '/../themes/';

$sami['template_dirs'] = $templates;

现在, 我们在应用程序的根目录存在 themes 文件夹, 创建一个新主题, 我们需要创建一个文件夹, 并且在文件夹中放置一个 manifest.yml

1
2
3
4
// themes/laravel/manifest.yml

name: laravel
parent: default

我们的新主题的文件名是 laravel , 他继承于 default 主题属性. 作为示例, 我们添加一些资源文件, 并且覆盖掉默认模版的一些样式.

添加资源

我们在创建的主题文件夹中创建一个 css 文件夹, 并且在 css 文件夹中创建一个  laravel.css   文件.

1
2
3
4
5
// themes/laravel/css/laravel.css
...
#api-tree a {
color: #F4645F;
}
1
2
3
4
// themes/laravel/manifest.yml
...
static:
'css/laravel.css': 'css/laravel.css'

这个静态文件配置块, 告诉 Sami , 复制文件到指定的目录, 新的构建目录中会包含新的文件 build/laravel/%version%/css/laravel.css.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// themes/laravel/manifest.yml
...
global:
'layout/base.twig': 'layout/base.twig'


// themes/laravel/layout/base.twig
...
{% extends 'default/layout/base.twig' %}

{% block head %}
{{ parent() }}
<link rel="stylesheet" href="css/laravel.css" />
{% endblock %}

每次 Sami 想加载 base 布局, 他会使用你主题中定义的文件. 我们扩展默认的基本布局来保持默认的外观. 然后我们插入自定义样式到 head 部分. 调用 parent() 函数将保持已经存在的内容在 head 区块中, 并且在 link 标签前输出.

1
2
3
4
5
6
// config/config.php

$options = [
'theme' => 'laravel',
//...
];
1
php vendor/sami/sami/sami.php render config/config.php --force

默认 , Sami 不会在文档未发生任何变化的时候重载. 然而使用 --force 标记会强制输出文件. 在浏览器查看文档页面, 注意下左侧导航的颜色是不是已经改变.

变更标记

1
2
3
4
// themes/laravel/manifest.yml

global:
'namespaces.twig': 'namespaces.twig'

作为推荐名称, namespaces 文件定义了我们的命名空间内容是怎么被渲染的.

1
2
3
4
// themes/laravel/namespaces.twig

{% extends 'default/namespaces.twig' %}
{% from "macros.twig" %}

如果你打开 default/namespaces.twig 页面, 你会发现 Sami 正在使用 macros 来简化扩展模版的进度. 我们会创建我们自己的  macros.twig 文件来覆盖默认的标记.

因为 Twig 不支持在 macro 中继承和覆盖重写, 我们将复制并且粘贴默认的主题 macros 并开始编辑他们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// themes/laravel/macros.twig

{% macro render_classes(classes) -%}
<div class="container-fluid underlined">
{% for class in classes %}
<div class="row">
<div class="col-md-6">
{% if class.isInterface %}
<span class="label label-primary">I</span>
{% else %}
<span class="label label-info">C</span>
{% endif %}

{{ _self.class_link(class, true) }}
</div>
<div class="col-md-6">
{{ class.shortdesc|desc(class) }}
</div>
</div>
{% endfor %}
</div>
{%- endmacro %}

我们仅仅在这个 macro 中改变了一件事, 就是区分了 ClassInterface . Sami 用来重点标识接口但是我们在每一个条目前都标识了一个带颜色的标签.
我们没有在这个页面改变很多东西. 但是你可能会在你的页面中使用 bootstrap 样式, 让菜单更加响应化或者其他的.
现在, 在重新生成文档之后你会发现你列出了一个 namespace, class 和 interface 都使用标签标识出来了.

概述

在这个文档中, 我们介绍了一个新的 Symfony 工具来帮助你管理包文档. 你同样可以创建一个独一无二的主题. 你可以找到我们文档的最终例子 -> Github.如果有什么问题可以给我们留言. 谢谢

肉眼看到的相同两个字串的不同

祭出两个相同(其实不同)的字符串

1
2
$strA = '8888‬';
$strB = '8888';

我们肉眼看到的这两个字符串是绝对相同的, 对的, 他是一个’8888’, 让我们用 php (世界上最好的语言) 输出下两个字串的长度

1
2
3
4
5
6
7
# code
var_dump($strA);
var_dump($strB);

# result
string(7) "8888‬"
string(4) "8888"

我滴妈呀, 怎么会不同呢, 纳闷..

让我们撕下她的伪装

1
2
3
for ($i = 0, $iMax = strlen($strA); $i < $iMax; $i++) {
var_dump($strA[$i]);
}
1
2
3
4
5
6
7
string(1) "8"
string(1) "8"
string(1) "8"
string(1) "8"
string(1) "�"
string(1) "�"
string(1) "�"

咦, 这是个什么鬼. 不像是正常字符啊. 哦, 对了, 我们是用的Utf-8 字符集, 这三个应该是一个字符, 我们把它组合起来

获取到 ASCII 码值

1
2
3
for ($i = 0, $iMax = strlen($strA); $i < $iMax; $i++) {
var_dump(ord($strA[$i]));
}
1
2
3
4
5
6
7
int(56)
int(56)
int(56)
int(56)
int(226)
int(128)
int(172)

这个编码值是 [226 128 172], 让我们找到它.

看, 在这里

31a1858d413ad156f113694da1f6f6aa.jpeg

来自于这个网站, 好像是输出格式化标识符. 以上.

Spyc - 一个简单的PHP Yaml 类

1
2
3
4
5
6
# S P Y C
# a simple php yaml class
#
# license: [MIT License, http://www.opensource.org/licenses/mit-license.php]
# url : https://github.com/mustangostang/spyc
#

下载地址: https://github.com/mustangostang/spyc

1
2
3
4
5
6
7
8
9
10
11
12
include('../spyc.php');

$array = Spyc::YAMLLoad('../spyc.yaml');

echo '<pre><a href="spyc.yaml">spyc.yaml</a> loaded into PHP:<br/>';
print_r($array);
echo '</pre>';


echo '<pre>YAML Data dumped back:<br/>';
echo Spyc::YAMLDump($array);
echo '</pre>';

[原] 怎样创建 PSR-4 的 Php 包

本文是帮助初学者搭建基础的 php composer 包, 本项目源码地址

目录结构和初建准备

首先创建一个目录来存放所有文件, 这里我 命名为 util-demo , 目录中需要包含两个目录 src/, tests/, 所有的代码需要放置到 src/ 目录, 所有的测试文件需要放置到 tests/ 目录

初始化文件夹, 并使用 git 初始化项目, 便于进行版本管理

1
2
3
$ mkdir util-demo
$ cd util-demo
$ git init

初始化 composer

这里假定你已经安装了 composer, 如果没有安装, 请到 https://getcomposer.org/download/ 进行安装

初始化并输入包名, 这里默认使用 duoli/util-demo 包名

1
2
3
4
5
6
7
$ composer init

Welcome to the Composer config generator

This command will guide you through creating your composer.json config.

Package name (<vendor>/<name>) [duoli/util-demo]:

输入描述

1
Description []: first util package demo

输入作者名称, n 跳过

1
Author [duoli <zhaody901@126.com>, n to skip]:

设定包的稳定版本, 默认是 stable, 详细查看 minimum-stability (root-only)

1
Minimum Stability []:

安装类型 : Package Type, 默认是 library

1
Package Type (e.g. library, project, metapackage, composer-plugin) []:

License, 协议, 关于协议部分, 你可以选择自己需要的协议, 阮老师这里有一个阅读指南, 可以进行参考 如何选择开源许可证?, 对于协议部分的写法可以参考 许可协议 license 这里

1
License []:

require

设定三方依赖, 表明当前项目/包是否有依赖于其他包进行开发, 这里选择 no,

1
2
3
4
Define your dependencies.

# 生产依赖
Would you like to define your dependencies (require) interactively [yes]?

require-dev

开发依赖, 这里我们加入 phpunit 作为单元测试依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Would you like to define your dev dependencies (require-dev) interactively [yes]?

Search for a package: phpunit

Found 15 packages matching phpunit

[0] phpunit/phpunit
...
[14] brianium/paratest

Enter package # to add, or the complete package name if it is not listed: 0
Enter the version constraint to require (or leave blank to use the latest version):
Using version ^8.5 for phpunit/phpunit
Search for a package:

当不需要额外增加包的时候, 直接回车, 确认安装包依赖, 初始化完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"name": "duoli/util-demo",
"description" : "first util package demo",
"require-dev": {
"phpunit/phpunit": "^8.5"
},
"license": "MIT",
"authors": [
{
"name": "duoli",
"email": "zhaody901@126.com"
}
],
"require": {}
}

Do you confirm generation [yes]?

Would you like to install dependencies now [yes]? yes
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 29 installs, 0 updates, 0 removals
- Installing sebastian/version (2.0.1): Downloading (100%)
......
- Installing phpunit/phpunit (8.5.8): Downloading (100%)
sebastian/global-state suggests installing ext-uopz (*)
phpunit/phpunit suggests installing phpunit/php-invoker (^2.0.0)
Package phpunit/php-token-stream is abandoned, you should avoid using it. No replacement was suggested.
Writing lock file
Generating autoload files
5 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

上边初始化完成之后, 我们文件目录应该如下所示

1
2
3
4
.
├── composer.json
├── composer.lock
└── vendor

作为包管理来讲, 我们需要忽略 composer.lockvendor 文件夹, 因为在项目中使用此包的时候会进行全局的安装, 不提交是为了不产生冗余代码.

我们加入 .gitignore 文件, 加入这两个文件夹的忽略, 另外由于要生成 phpunit 测试文件, 我们还需要加入 .phpunit.* , 我这里还额外加入了 IDE 的忽略

.gitignore

1
2
3
4
5
6
7
8
9
# ide
.idea

# project
composer.lock
vendor/

# phpunit
.phpunit.*

文件自动加载 autoload

autoload

这里是上边初始化没有提及到的, autoload 指定了包的加载方式, Composer 可以自动加载包内文件, 但是你需要指定你的文件的加载方式, 这里我们使用的是 psr-4 标准, 详细的规范可以阅读 PSR-4 自动加载规范

这里需要在 composer.json 中追加

1
2
3
4
5
6
7
8
  ...
"autoload": {
"psr-4": {
"Duoli\\UtilDemo\\": "src",
"Duoli\\UtilDemoTests\\": "tests"
}
}
...

因为这个加载是后续添加的, 想要识别需要我们手动运行下 composer dumpautoload, 这样才可以识别我们添加的命名空间映射

另这里我额外约定了一个测试的命名空间, 方便对测试文件进行命名空间的目录识别

travis 测试集成

如果打算在GitHub上托管包,一个不错优点是集成Travis CI,这是一个持续集成应用程序,它会自动运行单元测试文件. 当接受 pr 时,会非常有用,因为你可以快速查看是否所有的测试都通过验证。

要与 Travis 集成,添加一个名为 .travis.yml 的文件,并复制以下内容

1
2
3
4
5
6
7
8
9
10
11
language: php

php:
– 7.0
– hhvm

before_script:
– composer self-update
– composer install –prefer-source –no-interaction –dev

script: phpunit

在这里,不会深入讨论使用 Travis 的问题,但这应该是一个很好的开始,你可以自己来进行研究如何使用它.

编写代码

现在可以编写代码了, 打开 src 目录创建 File.php 文件, 复制如下代码

1
2
3
4
5
6
7
8
9
class File
{

public function extension($filename)
{
return pathinfo($filename, PATHINFO_EXTENSION);
}

}

如果你曾经查看过旧的 PHP 包源码,可能会发现一个 Duoli/UtilDemo 目录。PSR-4 不再使用嵌套的目录结构,而是允许直接在 src 目录下编写类

编写单元测试

现在我们已经编写了包源码,可以开始编写一些测试了。PHPUnit 需要一个名为 phpunit.xml 的文件来定义一些设置。在根目录中创建一个新的phpunit.xml文件,并复制以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<phpunit
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Duoli Util Test Suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
</phpunit>

如果不熟悉这个文件中意思,不用担心,你无需关注设置了哪些内容(当前来讲), 这个文件只是定义一些设置和文件应该如何自动加载的约定.

接下来,在 tests 目录下,创建一个名为 StrTest.php的文件,并复制以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php namespace Duoli\UtilDemoTests;

use Duoli\UtilDemo\File;
use PHPUnit\Framework\TestCase;

class FileTest extends TestCase
{

public function testExtension()
{
$extension = File::extension('readme.md');
$this->assertEquals('md', $extension);
}

}

打开命令行并运行:

1
$ ./vendor/bin/phpunit

PHPUnit 会自动运行测试,并给出一个绿色的 OK (1 test, 1 assertion) 输出。

在编写测试文件时,需要确保测试类继承了 PHPUnit\Framework\TestCase

文档

最后要做的是创建一个 Readme.md 的文档。如果打算在 GitHub 上托管代码,这个文件会在代码页面显示出来,目的是描述包以及包如何工作.

还应该在源代码中包含一个许可证。默认情况下,你发布的任何东西都是你的版权所有,如果你想让其他人使用你的代码,你需要给它一个许可。一个好的选择是 MIT 许可

发布到 github 和 Packagist

现在已经完成了你的包,你可以将它推到 GitHub 和 Packagist。

GitHub 是一个托管的 git 存储库服务,它使得在软件协作变得非常容易。

Packagist 是查找和使用 PHP 包的服务商

要将代码推送到 GitHub,创建一个新的存储库, 然后设定 git 的远端, 将代码推上去即可, 命令是

1
2
$ git remote add origin git@github.com:username/demo.git
$ git push -u origin master

在 Github 上集成 Packagist 和 Travis, 需要找到设置, 并且在  Webhooks & Services 部分找到这两个服务, 集成这两个服务需要授权 github 账户并且设置一些简单的步骤.

概括

如果以前从未开发过已发布的 php 包,那么需要考虑很多问题。然而,一旦你操作过不止一次就不至于这么复杂了.

PHP 的包结构非常简单,而 PSR-4 使其更加简单。一旦了解了该结构的每一部分是用于做什么,其他人阅读 PHP 包就会容易很多.

参考文章

Faq

Laravel 错误 Class log does not exist …

Fatal error: Uncaught exception ‘ReflectionException’ with message ‘Class log does not exist’ in /Users/freek/dev/laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php:776

出现这种问题的原因是不能够加载 log 方法. 原因是在加载的时候会加载 config 文件的数据, 而 config 文件中的配置是批量加载的, 所以在自己加载的时候 config 文件的写法不支持自定义的函数变量/ 常量/ 自定义方法.

所以从配置文件入手, 删除未加载的配置文件, 删除未导入包的配置文件.

这种问题一般出现在 复制项目, 并且删除了包的情况下.

参考文章:

[Plugin] 第三方包

排名 扩展包 一句话描述
1 intervention/image 图片处理扩展包,支持裁剪、水印等处理,使用教程请见  https://phphub.org/topics/1903
2 barryvdh/laravel-debugbar 页面调试工具栏 (对 phpdebugbar 的封装),教程请见:https://phphub.org/topics/2531
3 barryvdh/laravel-ide-helper 使用 IDE 开发 Laravel 项目的好帮手,支持 Facade 方法跳转,相关讨论请见:https://phphub.org/topics/2532
4 maatwebsite/excel Excel 处理工具,中文处理时会出现乱码,推荐使用  laravel-snappy,历史讨论请见  https://phphub.org/topics/2477
5 aws/aws-sdk-php-laravel 亚马逊 AWS 服务的开发者工具包,亚马逊云已经在 2016 年 8 月  正式落地中国,这个包以后会常用到,教程请见:https://phphub.org/topics/2533
6 jenssegers/agent 客户端 User Agent 解析工具(基于 Mobiledetect),教程请见:https://phphub.org/topics/782
7 bugsnag/bugsnag-laravel Bugsnag 服务集成包(异常捕获服务,可惜国内访问效果不好),教程请见:https://phphub.org/topics/2534
8 zizaco/entrust 基于用户组的用户权限系统(必备),教程请见:https://phphub.org/topics/166
9 barryvdh/laravel-cors 跨域资源共享的支持
10 barryvdh/laravel-dompdf PDF 操作工具(基于 dompdf )
11 laravelbook/ardent 自动  数据模型  验证工具
12 tymon/jwt-auth JWT (JSON Web Token) 用户认证机制,示例项目  https://phphub.org/topics/2023
13 lucadegasperi/oauth2-server-laravel OAuth 2.0 支持,实例教程:https://phphub.org/topics/1792
14 maknz/slack Slack 服务的集成
15 jenssegers/mongodb MongoDB 数据库的支持 ,教程:https://phphub.org/topics/309
16 dingo/api 构建 API 服务器的完整解决方案,教程:https://phphub.org/topics/1159
17 itsgoingd/clockwork 配合 Chrome 浏览器下同名插件的调试工具,教程:https://phphub.org/topics/23
18 anahkiasen/underscore-php Underscore.js 类似的 PHP 语法支持
19 laracasts/generators Laracasts 出品的代码快速生成工具(推荐) ,使用教程:https://phphub.org/topics/2535
20 cviebrock/eloquent-sluggable 文章标题 URL 别名处理工具,教程:https://phphub.org/topics/1926
21 laracasts/testdummy Laracasts 出品的假数据创建工具
22 davejamesmiller/laravel-breadcrumbs 页面面包屑工具,教程:https://phphub.org/topics/1914
23 laracasts/utilities 将 PHP 变量转换为 JavaScript 变量
24 roumen/sitemap Sitemap 生成工具
25 yajra/laravel-datatables-oracle jQuery DataTables 的后端支持
26 webpatser/laravel-uuid RFC 4122 标准生成的 UUID ,使用教程  https://phphub.org/topics/2538
27 rcrowe/twigbridge Twig 模板引擎支持
28 intervention/imagecache 图片缓存增强工具
29 indatus/dispatcher 计划任务分发器(直接可替换掉 Cron),L5 内置了类似的功能
30 jenssegers/date 日期处理工具(让 Carbon 支持多语言,中文用户的福音)
31 rap2hpoutre/laravel-log-viewer 非常方便的页面 Log 查看工具,必备,不过使用时请注意访问权限控制
32 baum/baum 嵌套集合 (Nested Set) 模型的支持,教程:https://phphub.org/topics/2124
33 anahkiasen/rocketeer 现代化的服务器代码部署工具
34 anahkiasen/former 强大的表单构造器,教程请见  https://phphub.org/topics/2539
35 barryvdh/laravel-snappy HTML 生成 PDF/Image 工具(利用 wkhtmltopdf)
36 thujohn/twitter Twitter API 的支持
37 orchestra/testbench Laravel 扩展包的单元测试工具
38 graham-campbell/flysystem 文件系统操作,多平台支持(AWS,Dropbox 等)
39 mews/purifier 用户提交的 Html 白名单过滤,https://phphub.org/topics/36
40 laracasts/presenter Laracasts 出品的 Presenter 方案
41 venturecraft/revisionable 数据模型的操作记录(如管理员操作日记)
42 mcamara/laravel-localization Laravel 本地化功能增强
43 league/factory-muffin 允许更加方便的创建对象,一般在测试中常用(基本上是 ROR 的 factory_girl 的复制版)
44 robclancy/presenter Elequent 的 Presenter 方案
45 intouch/laravel-newrelic 应用状态监控服务 NewRelic 开发者工具包
46 xethron/migrations-generator 从现存的数据中以 migration 的形式导出数据库表,包括索引和外键,相当于  数据库迁移
47 greggilbert/recaptcha reCAPTCHA 验证码的支持
48 watson/validating 以 Trait 的方式来实现 Eloquent 数据模型保存的时候自动验证
49 dimsav/laravel-translatable 数据库的多语言翻译方案
50 laracasts/behat-laravel-extension Behat 测试框架的 Laravel 支持
51 jenssegers/rollbar Rollbar 错误监控服务的自动集成
52 torann/geoip 通过 IP 获取到对应的地理位置信息(GeoIP 数据库),请参考:https://phphub.org/topics/2537
53 davibennun/laravel-push-notification App 的 Push Notification 发送工具,支持苹果的 APNS 和 安卓的 GCM
54 chumper/zipper ZIp 打包工具(基于 ZipArchive)
55 simplesoftwareio/simple-qrcode 二维码生成工具
56 graham-campbell/markdown Markdown 解析器
57 aloha/twilio Twillio API 支持
58 propaganistas/laravel-phone 手机号码,电话号码验证支持
59 orangehill/iseed 将数据从数据库以 seed 的方式导出,数据填充  的逆向操作。(推荐)
60 sammyk/laravel-facebook-sdk (非官方)Laravel 的 Facebook 开发者工具包
61 vinkla/hashids Hash ID 生成器,方便把数字的 ID 隐藏(基于 Hashids),教程:https://phphub.org/topics/2536
62 spatie/laravel-backup 数据备份工具,支持压缩,支持各种文件系统(推荐)
63 mccool/laravel-auto-presenter 自动注入 Presenter,教程:https://phphub.org/topics/1267
64 graham-campbell/throttle 阀门控制工具
65 frozennode/administrator 快速创建基于数据模型的 CRUD 管理员后台,教程:https://phphub.org/topics/158https://phphub.org/topics/2407
66 codesleeve/laravel-stapler 专为 ORM 定制的文件上传支持
67 webpatser/laravel-countries 世界所有国家数据,包括首都汇率等
68 prettus/l5-repository Repository 开发模式的支持
69 pragmarx/google2fa 用户认证方案,支持谷歌提倡的双向认证和 HOTP 认证算法
70 hisorange/browser-detect 浏览器检测工具,包括客户端对 JavaScript 和 CSS 支持情况的检测,教程:https://phphub.org/topics/2046
71 graham-campbell/htmlmin 基于 minify 的 HTML 压缩工具
72 toin0u/geocoder-laravel 地理位置操作工具集(基于 Geocoder)
73 edvinaskrucas/notification 页面消息提醒的组件
74 laracasts/integrated PHPUnit 的集成测试支持
75 laravel/envoy Laravel 官方出品的简单的部署工具,教程:https://phphub.org/topics/24
76 felixkiss/uniquewith-validator 表单验证规则增加字段之间的唯一性验证
77 graham-campbell/exceptions 错误异常处理工具,支持开发和生产环境,使用 Whoops 进行错误显示
78 thomaswelton/laravel-gravatar Gravatar 服务的支持
79 mews/captcha 图片验证码方案
80 roumen/feed Feed 生成器
81 cviebrock/image-validator 表单验证增加图片专属,如长宽,比例等
82 laravelcollective/annotations 基于注解方式生成路由、事件、模型绑定的映射
83 gloudemans/shoppingcart 一个简单的购物车模块实现
84 artisaninweb/laravel-soap Soap 协议客户端
85 jlapp/swaggervel Swagger API 规范支持
86 barryvdh/laravel-translation-manager 翻译辅助工具,包含 Web 界面
87 patricktalmadge/bootstrapper Twitter Bootstrap 支持
88 soapbox/laravel-formatter 对不同输出格式进行转换,支持 Array,CSV,JSON,XML,YAML
89 fedeisas/laravel-mail-css-inliner 将 CSS 样式写入 HTML 里,用于邮件发送内容的样式定制
90 nicolaslopezj/searchable 以 Trait 的形式为 Eloquent 模型增加搜索功能
91 benconstable/phpspec-laravel PHPSpec BDD 测试框架的 Laravel 扩展
92 watson/rememberable 让 Laravel 5 数据模型支持 remember() 方法
93 rtconner/laravel-tagging 为 Eloquent 模型增加打标签功能
94 laravelcollective/remote LaravelCollective 维护的 SSH 连接管理工具
95 khill/lavacharts Google 图表 JavaScript API 的封装
96 anchu/ftp 让 Laravel 支持 FTP 操作
97 liebig/cron 计划任务分发器(直接可替换掉 Cron),L5 内置了类似的功能
98 lord/laroute JavaScript 读取路由信息的解决方案
99 spatie/laravel-analytics Google 统计数据获取工具
100 hieu-le/active 非常方便的方案来判断导航元素的  active  状态

[转+] Carbon 中文文档

原文地址 : Carbon中文文档

介绍

Carbon 是php的日期处理类库(A simple PHP API extension for DateTime.)。

Carbon 继承自PHP的 DateTime

1
2
3
4
5
6
7
<?php
namespace Carbon;

class Carbon extends \DateTime
{
// code here
}

Carbon 继承了PHP的 Datetime 类,所以 Carbon 中没有涉及到的,但在 Datetime 中已经实现的方法都是可以使用的。

Carbon 类声明在 Carbon 命名空间下,可以通过引入命名空间的方式来代替每次输入完整的类名。

1
2
<?php
use Carbon\Carbon;

Note:如果在使用 Carbon 时,没有专门设置时区的话,默认使用 America/Toronto 的时区。

要特别留意是否使用了正确的时区,比如 Carbon 的所有差异比较都使用 UTC 或者系统设定的时区。

1
2
3
4
5
<?php
$dtToronto = Carbon::createFromDate(2012, 1, 1, 'America/Toronto');
$dtVancouver = Carbon::createFromDate(2012, 1, 1, 'America/Vancouver');

echo $dtVancouver->diffInHours($dtToronto); // 3

以上进行的时间比较是在提供的 Carbon 实例所在的时区下完成的。例如作者所在的时区为 东京时间减13 小时,因此在下午一点后。Carbon::now(‘Asia/Tokyo’)->isToday() 将会返回 false ,如果在调用 now() 时设置时区为东京时区,接下来的操作都使用东京时区是说不过去的。所以在与 now() 创建的实例进行比较时,默认是在当前时区下完成的。

初始化

有好几种方式可以创建 Carbon 的实例,但是大家应该更倾向于通过这种语义化的静态方法来实现。

1
2
3
4
5
<?php
$carbon = new Carbon(); // equivalent to Carbon::now()
$carbon = new Carbon('first day of January 2008', 'America/Vancouver');
echo get_class($carbon); // 'Carbon\Carbon'
$carbon = Carbon::now(-5);

值得注意的是,Carbon 构造器的第二个参数被增强到了不仅限于是 DateTimeZone 实例,还可以是 String、Integer (表示相对于GMT的偏移值)。举个栗子来说明下 now() 方法。

1
2
3
4
5
6
7
8
9
10
<?php
$now = Carbon::now();

$nowInLondonTz = Carbon::now(new DateTimeZone('Europe/London'));

// or just pass the timezone as a string
$nowInLondonTz = Carbon::now('Europe/London');

// or to create a date with a timezone of +1 to GMT during DST then just pass an integer
echo Carbon::now(1)->tzName; // Europe/London

你将会喜欢上用 parse() 方法来代替原有繁琐的构造方式

1
2
3
<?php
echo (new Carbon('first day of December 2008'))->addWeeks(2); // 2008-12-15 00:00:00
echo Carbon::parse('first day of December 2008')->addWeeks(2); // 2008-12-15 00:00:00

类似 now() 这样直接返回 Carbon 实例的方法还有 today(), tomorrow(),yesterday(),他们都接受一个 timezone 类型的参数,最后得到的结果时间部分都是 00:00:00

1
2
3
4
5
6
7
8
9
<?php
$now = Carbon::now();
echo $now; // 2016-06-24 15:18:34
$today = Carbon::today();
echo $today; // 2016-06-24 00:00:00
$tomorrow = Carbon::tomorrow('Europe/London');
echo $tomorrow; // 2016-06-25 00:00:00
$yesterday = Carbon::yesterday();
echo $yesterday; // 2016-06-23 00:00:00

下面是一些其他的 creatXXX() 形式的静态方法。绝大多数静态方法的参数是可传可不传的,如果不传的话会使用方法预设的默认值,这些预设值一般都是针对当前日期、时间、时区的。如果为传递某个必要参数,会抛出一个 InvalidArgumentException 类型的异常,用 DateTime::getLastErrors() 方法可以得到异常的详细信息。

1
2
3
4
<?php
Carbon::createFromDate($year, $month, $day, $tz);
Carbon::createFromTime($hour, $minute, $second, $tz);
Carbon::create($year, $month, $day, $hour, $minute, $second, $tz);

createFromDate() 默认返回当前时间,createFromTime()日期默认是今天。crete() 所有为 null 的参数都将默认为当前对应的时间。同样,时区也默认是当前时区。如果只设置了小时数没有设置分秒那么分秒默认是 0

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$xmasThisYear = Carbon::createFromDate(null, 12, 25); // Year defaults to current year
$Y2K = Carbon::create(2000, 1, 1, 0, 0, 0);
$alsoY2K = Carbon::create(1999, 12, 31, 24);
$noonLondonTz = Carbon::createFromTime(12, 0, 0, 'Europe/London');

// A two digit minute could not be found
try {
Carbon::create(1975, 5, 21, 22, -2, 0);
} catch(InvalidArgumentException $x) {
echo $x->getMessage();
}
1
2
<?php
Carbon::createFromFormat($format, $time, $tz);

createFromFormat() 与php的DateTime::createFromFormat。不同之处是 $dt 参数可以是 DateTImeZone 的实例或者一个时区的字符串。并且可以会返回参数异常的提示。从createXX()的源码可以看出,他们都调用了createFromFormat()方法。

1
2
<?php
echo Carbon::createFromFormat('Y-m-d H', '1975-05-21 22')->toDateTimeString(); // 1975-05-21 22:00:00

最后提到的这两个create方法都是处理Unix时间戳的。第一个将会返回一个等于预期时间戳的 Carbon 实例,时区可以设置也可以选用默认值。第二个方法,createFromTimestampUTC() 与第一个不同的是时区将始终是 UTC(GMT) .第一个方法的第二个示例,只是为了让这个函数的用法展现的更加明确。Negative timestamps are also allowed.

1
2
3
4
<?php
echo Carbon::createFromTimestamp(-1)->toDateTimeString(); // 1969-12-31 18:59:59
echo Carbon::createFromTimestamp(-1, 'Europe/London')->toDateTimeString(); // 1970-01-01 00:59:59
echo Carbon::createFromTimeStampUTC(-1)->toDateTimeString(); // 1969-12-31 23:59:59

copy() 方法可以copy一个已经存在的 Carbon 实例。对copy生成实例进行修改并不会影响被copy对象的本身。

1
2
3
4
5
<?php
$dt = Carbon::now();
echo $dt->diffInYears($dt->copy()->addYear()); // 1

// $dt was unchanged and still holds the value of Carbon:now()

最后,当你正在使用的 DateTime 实例是通过实例化其他继承了 DateTime 库而得到的,别怕!通过下边的方式仍然可以极其友好创建 Carbon 实例。

1
2
3
4
5
<?php
$dt = new \DateTime('first day of January 2008'); // <== instance from another API
$carbon = Carbon::instance($dt);
echo get_class($carbon); // 'Carbon\Carbon'
echo $carbon->toDateTimeString(); // 2008-01-01 00:00:00

关于毫秒的一些处理。php自带的 DateTime 类也可以设置毫秒,但是在进行日期的数学预算时并不会考虑毫秒。从 Carbon 1.12.0版本起,实例化、copy也能像 format() 方法一样支持毫秒(PHP默认的只有 Datetime::format() 支持毫秒)。

1
2
3
4
<?php
$dt = Carbon::parse('1975-05-21 22:23:00.123456');
echo $dt->micro; // 123456
echo $dt->copy()->micro; // 123456

获取PHP支持的有效时间取值范围:最早时间、最晚时间

1
2
3
<?php
echo Carbon::maxValue(); // '2038-01-18 22:14:07'
echo Carbon::minValue(); // '1901-12-13 15:45:52'

本地化

Carbon中,formatLocalized() 方法通过调用 strftime() 方法,弥补了php底层的 DateTime 类不支持区域化设置的缺陷。如果你已经通过使用 setlocale() 方法设置过当前区域,formatLocalized($format) 方法将会按照设置的区域格式进行返回。

1
2
3
4
5
<?php
setlocale(LC_TIME, 'German');
echo $dt->formatLocalized('%A %d %B %Y'); // Mittwoch 21 Mai 1975
setlocale(LC_TIME, '');
echo $dt->formatLocalized('%A %d %B %Y'); // Wednesday 21 May 1975

diffForHumans() 的结果也会被转化成区域语言。通过Carbon::setLocale() 方法可以设置 Carbon 的区域语言。

1
2
3
4
5
<?php
Carbon::setLocale('de');
echo Carbon::now()->addYear()->diffForHumans(); // in 1 Jahr

Carbon::setLocale('en');

注意:如果在linux系统中转换出现了问题,请仔细检查安装在本地或生产系统中语言环境。

1
2
3
locale -a 列举出所有可用的语言环境
sudo locale-gen zh_CN.utf8 安装新的语言环境
sudo dpkg-reconfigure locales 配置启用新的语言环境,并重启

测试 Aids

通过测试方法可以得到一个模拟或真实的 Carbon 实例。只有在一下情况下,主动提供的 Carbon 实例才会被特殊处理:

  • 调用静态方法 now(),例如: Varbon::now()
  • 传给 construct 或 parse() 方法的是 null (或空字符串),例如:new Carbon(null)
  • 当传给 construct 或 parse()的是字符串 now,例如:new Carbon(‘now’)
1
2
3
4
5
6
7
8
9
10
11
$knownDate = Carbon::create(2001, 5, 21, 12);          // create testing date
Carbon::setTestNow($knownDate); // set the mock (of course this could be a real mock object)
echo Carbon::now(); // 2001-05-21 12:00:00
echo new Carbon(); // 2001-05-21 12:00:00
echo Carbon::parse(); // 2001-05-21 12:00:00
echo new Carbon('now'); // 2001-05-21 12:00:00
echo Carbon::parse('now'); // 2001-05-21 12:00:00
var_dump(Carbon::hasTestNow()); // bool(true)
Carbon::setTestNow(); // clear the mock
var_dump(Carbon::hasTestNow()); // bool(false)
echo Carbon::now();

有用的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class SeasonalProduct
{
protected $price;

public function __construct($price)
{
$this->price = $price;
}

public function getPrice() {
$multiplier = 1;
if (Carbon::now()->month == 12) {
$multiplier = 2;
}

return $this->price * $multiplier;
}
}

$product = new SeasonalProduct(100);
Carbon::setTestNow(Carbon::parse('first day of March 2000'));
echo $product->getPrice(); // 100
Carbon::setTestNow(Carbon::parse('first day of December 2000'));
echo $product->getPrice(); // 200
Carbon::setTestNow(Carbon::parse('first day of May 2000'));
echo $product->getPrice(); // 100
Carbon::setTestNow();

一些相关的用法也可以得到一个模拟的 now 实例,返回相应的模拟数据。

1
2
3
4
5
6
7
8
$knownDate = Carbon::create(2001, 5, 21, 12);          // create testing date
Carbon::setTestNow($knownDate); // set the mock
echo new Carbon('tomorrow'); // 2001-05-22 00:00:00 ... notice the time !
echo new Carbon('yesterday'); // 2001-05-20 00:00:00
echo new Carbon('next wednesday'); // 2001-05-23 00:00:00
echo new Carbon('last friday'); // 2001-05-18 00:00:00
echo new Carbon('this thursday'); // 2001-05-24 00:00:00
Carbon::setTestNow();

以下是当前支持的时间转换字

  • this
  • net
  • last
  • this
  • next
  • last
  • tomorrow
  • yesterday
  • +
  • -
  • first
  • last
  • ago

值得注意的是像 next() , previous() 和 modify() 方法等相关的修改会把日期的时间部分设置成 00:00:00 。

获取

获取器通过PHP的 __get() 方式实现。可以直接通过一下方式直接获取到属性的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
$dt = Carbon::parse('2012-9-5 23:26:11.123789');

// These getters specifically return integers, ie intval()
var_dump($dt->year); // int(2012)
var_dump($dt->month); // int(9)
var_dump($dt->day); // int(5)
var_dump($dt->hour); // int(23)
var_dump($dt->minute); // int(26)
var_dump($dt->second); // int(11)
var_dump($dt->micro); // int(123789)
var_dump($dt->dayOfWeek); // int(3)
var_dump($dt->dayOfYear); // int(248)
var_dump($dt->weekOfMonth); // int(1)
var_dump($dt->weekOfYear); // int(36)
var_dump($dt->daysInMonth); // int(30)
var_dump($dt->timestamp); // int(1346901971)
var_dump(Carbon::createFromDate(1975, 5, 21)->age); // int(41) calculated vs now in the same tz
var_dump($dt->quarter); // int(3)

// Returns an int of seconds difference from UTC (+/- sign included)
var_dump(Carbon::createFromTimestampUTC(0)->offset); // int(0)
var_dump(Carbon::createFromTimestamp(0)->offset); // int(-18000)

// Returns an int of hours difference from UTC (+/- sign included)
var_dump(Carbon::createFromTimestamp(0)->offsetHours); // int(-5)

// Indicates if day light savings time is on
var_dump(Carbon::createFromDate(2012, 1, 1)->dst); // bool(false)
var_dump(Carbon::createFromDate(2012, 9, 1)->dst); // bool(true)

// Indicates if the instance is in the same timezone as the local timezone
var_dump(Carbon::now()->local); // bool(true)
var_dump(Carbon::now('America/Vancouver')->local); // bool(false)

// Indicates if the instance is in the UTC timezone
var_dump(Carbon::now()->utc); // bool(false)
var_dump(Carbon::now('Europe/London')->utc); // bool(false)
var_dump(Carbon::createFromTimestampUTC(0)->utc); // bool(true)

// Gets the DateTimeZone instance
echo get_class(Carbon::now()->timezone); // DateTimeZone
echo get_class(Carbon::now()->tz); // DateTimeZone

// Gets the DateTimeZone instance name, shortcut for ->timezone->getName()
echo Carbon::now()->timezoneName; // America/Toronto
echo Carbon::now()->tzName; // America/Toronto

设置

Setters 通过PHP的 __set() 方法实现。值得注意的是,通过这种方式设置时间戳时,时区不会相对于时间戳而改变。如果需要改变时区的话,需要针对时区单独设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$dt = Carbon::now();

$dt->year = 1975;
$dt->month = 13; // would force year++ and month = 1
$dt->month = 5;
$dt->day = 21;
$dt->hour = 22;
$dt->minute = 32;
$dt->second = 5;

$dt->timestamp = 169957925; // This will not change the timezone

// Set the timezone via DateTimeZone instance or string
$dt->timezone = new DateTimeZone('Europe/London');
$dt->timezone = 'Europe/London';
$dt->tz = 'Europe/London';

快捷设置 (Fluent Setters)

此处 Setters 方法的参数是必选参数,Carbon 提供了更多种设置方式可供使用。值得注意的是,所有对于时区的修改都会影响整个到 Carbon 实例。对时间戳进行修改时不会自动转换到时间戳对应的时区。

1
2
3
4
5
6
7
8
9
$dt = Carbon::now();

$dt->year(1975)->month(5)->day(21)->hour(22)->minute(32)->second(5)->toDateTimeString();
$dt->setDate(1975, 5, 21)->setTime(22, 32, 5)->toDateTimeString();
$dt->setDateTime(1975, 5, 21, 22, 32, 5)->toDateTimeString();

$dt->timestamp(169957925)->timezone('Europe/London');

$dt->tz('America/Toronto')->setTimezone('America/Vancouver');

检查属性是否调用

当尝试调用 Carbon 实例的属性时,会首先检查该属性是否存在,存在返回 true,不存在返回 false。

1
2
3
4
var_dump(isset(Carbon::now()->iDoNotExist));       // bool(false)
var_dump(isset(Carbon::now()->hour)); // bool(true)
var_dump(empty(Carbon::now()->iDoNotExist)); // bool(true)
var_dump(empty(Carbon::now()->year)); // bool(false)

字串格式化

所有类似 toXXXString() 这样的方法都依赖于DateTime::format()。__toString() 方法允许 Carbon 实例被打印时以一种可读性更好的方式被打印出来。

1
2
3
4
5
6
7
8
9
10
11
$dt = Carbon::create(1975, 12, 25, 14, 15, 16);

var_dump($dt->toDateTimeString() == $dt); // bool(true) => uses __toString()
echo $dt->toDateString(); // 1975-12-25
echo $dt->toFormattedDateString(); // Dec 25, 1975
echo $dt->toTimeString(); // 14:15:16
echo $dt->toDateTimeString(); // 1975-12-25 14:15:16
echo $dt->toDayDateTimeString(); // Thu, Dec 25, 1975 2:15 PM

// ... of course format() is still available
echo $dt->format('l jS \\of F Y h:i:s A'); // Thursday 25th of December 1975 02:15:16 PM

另外可以默认设置 __toString() 方法所要显示的时间日期格式。

1
2
3
4
Carbon::setToStringFormat('jS \o\f F, Y g:i:s a');
echo $dt; // 25th of December, 1975 2:15:16 pm
Carbon::resetToStringFormat();
echo $dt; // 1975-12-25 14:15:16

如果需要设定特定的语言显示,请参考 Localization 部分。

通用格式化

下面是对 DateTime 类提供的通用格式的一些封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$dt = Carbon::now();

// $dt->toAtomString() is the same as $dt->format(DateTime::ATOM);
echo $dt->toAtomString(); // 1975-12-25T14:15:16-05:00
echo $dt->toCookieString(); // Thursday, 25-Dec-1975 14:15:16 EST
echo $dt->toIso8601String(); // 1975-12-25T14:15:16-0500
echo $dt->toRfc822String(); // Thu, 25 Dec 75 14:15:16 -0500
echo $dt->toRfc850String(); // Thursday, 25-Dec-75 14:15:16 EST
echo $dt->toRfc1036String(); // Thu, 25 Dec 75 14:15:16 -0500
echo $dt->toRfc1123String(); // Thu, 25 Dec 1975 14:15:16 -0500
echo $dt->toRfc2822String(); // Thu, 25 Dec 1975 14:15:16 -0500
echo $dt->toRfc3339String(); // 1975-12-25T14:15:16-05:00
echo $dt->toRssString(); // Thu, 25 Dec 1975 14:15:16 -0500
echo $dt->toW3cString(); // 1975-12-25T14:15:16-05:00

比较

通过以下方式可以对两个 Carbon 实例进行简单的比较。牢记这些比较都是在UTC时区下完成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
echo Carbon::now()->tzName;                        // America/Toronto
$first = Carbon::create(2012, 9, 5, 23, 26, 11);
$second = Carbon::create(2012, 9, 5, 20, 26, 11, 'America/Vancouver');

echo $first->toDateTimeString(); // 2012-09-05 23:26:11
echo $first->tzName; // America/Toronto
echo $second->toDateTimeString(); // 2012-09-05 20:26:11
echo $second->tzName; // America/Vancouver

var_dump($first->eq($second)); // bool(true)
var_dump($first->ne($second)); // bool(false)
var_dump($first->gt($second)); // bool(false)
var_dump($first->gte($second)); // bool(true)
var_dump($first->lt($second)); // bool(false)
var_dump($first->lte($second)); // bool(true)

$first->setDateTime(2012, 1, 1, 0, 0, 0);
$second->setDateTime(2012, 1, 1, 0, 0, 0); // Remember tz is 'America/Vancouver'

var_dump($first->eq($second)); // bool(false)
var_dump($first->ne($second)); // bool(true)
var_dump($first->gt($second)); // bool(false)
var_dump($first->gte($second)); // bool(false)
var_dump($first->lt($second)); // bool(true)
var_dump($first->lte($second)); // bool(true)

如果要判断当前实例对应的时间,是否在其他两个实例对应的时间之间,可以用 between() 方法。如果提供了第三个参数,并且为 true,将会进行 >= 和 和  
这些方法会在两个时间差值后增加一写描述,可能类似下边这四种:

  • When comparing a value in the past to default now:

    • 1 hour ago
    • 5 months ago
  • When comparing a value in the future to default now:

    • 1 hour from now
    • 5 months from now
  • When comparing a value in the past to another value:

    • 1 hour before
    • 5 months before
  • When comparing a value in the future to another value:

    • 1 hour after
    • 5 months after

你也可以传递第二个参数去掉类似 ago,from now 这种修饰符,类似这样的用法 diffForHumans(Carbon $other, true) 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// The most typical usage is for comments
// The instance is the date the comment was created and its being compared to default now()
echo Carbon::now()->subDays(5)->diffForHumans(); // 5 days ago

echo Carbon::now()->diffForHumans(Carbon::now()->subYear()); // 1 year after

$dt = Carbon::createFromDate(2011, 8, 1);

echo $dt->diffForHumans($dt->copy()->addMonth()); // 1 month before
echo $dt->diffForHumans($dt->copy()->subMonth()); // 1 month after

echo Carbon::now()->addSeconds(5)->diffForHumans(); // 5 seconds from now

echo Carbon::now()->subDays(24)->diffForHumans(); // 3 weeks ago
echo Carbon::now()->subDays(24)->diffForHumans(null, true); // 3 weeks

你也通过在 diffForHumans() 被调用前,使用 Carbon::setLocale(‘fr’) 来改变语言设置。详细请参考 localization 部分。

修改

Carbon argument.这些方法组对修改当前实例很有帮助。你会注意到 startOfXXX(),next(),和 previous() 方法将会设置时间为 00:00:00,另外 endOfXXX() 方法将会设置时间为 23:59:59 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
$dt = Carbon::create(2012, 1, 31, 12, 0, 0);
echo $dt->startOfDay(); // 2012-01-31 00:00:00

$dt = Carbon::create(2012, 1, 31, 12, 0, 0);
echo $dt->endOfDay(); // 2012-01-31 23:59:59

$dt = Carbon::create(2012, 1, 31, 12, 0, 0);
echo $dt->startOfMonth(); // 2012-01-01 00:00:00

$dt = Carbon::create(2012, 1, 31, 12, 0, 0);
echo $dt->endOfMonth(); // 2012-01-31 23:59:59

$dt = Carbon::create(2012, 1, 31, 12, 0, 0);
echo $dt->startOfYear(); // 2012-01-01 00:00:00

$dt = Carbon::create(2012, 1, 31, 12, 0, 0);
echo $dt->endOfYear(); // 2012-12-31 23:59:59

$dt = Carbon::create(2012, 1, 31, 12, 0, 0);
echo $dt->startOfDecade(); // 2010-01-01 00:00:00

$dt = Carbon::create(2012, 1, 31, 12, 0, 0);
echo $dt->endOfDecade(); // 2019-12-31 23:59:59

$dt = Carbon::create(2012, 1, 31, 12, 0, 0);
echo $dt->startOfCentury(); // 2000-01-01 00:00:00

$dt = Carbon::create(2012, 1, 31, 12, 0, 0);
echo $dt->endOfCentury(); // 2099-12-31 23:59:59

$dt = Carbon::create(2012, 1, 31, 12, 0, 0);
echo $dt->startOfWeek(); // 2012-01-30 00:00:00
var_dump($dt->dayOfWeek == Carbon::MONDAY); // bool(true) : ISO8601 week starts on Monday

$dt = Carbon::create(2012, 1, 31, 12, 0, 0);
echo $dt->endOfWeek(); // 2012-02-05 23:59:59
var_dump($dt->dayOfWeek == Carbon::SUNDAY); // bool(true) : ISO8601 week ends on Sunday

$dt = Carbon::create(2012, 1, 31, 12, 0, 0);
echo $dt->next(Carbon::WEDNESDAY); // 2012-02-01 00:00:00
var_dump($dt->dayOfWeek == Carbon::WEDNESDAY); // bool(true)

$dt = Carbon::create(2012, 1, 1, 12, 0, 0);
echo $dt->next(); // 2012-01-08 00:00:00

$dt = Carbon::create(2012, 1, 31, 12, 0, 0);
echo $dt->previous(Carbon::WEDNESDAY); // 2012-01-25 00:00:00
var_dump($dt->dayOfWeek == Carbon::WEDNESDAY); // bool(true)

$dt = Carbon::create(2012, 1, 1, 12, 0, 0);
echo $dt->previous(); // 2011-12-25 00:00:00

$start = Carbon::create(2014, 1, 1, 0, 0, 0);
$end = Carbon::create(2014, 1, 30, 0, 0, 0);
echo $start->average($end); // 2014-01-15 12:00:00

// others that are defined that are similar
// firstOfMonth(), lastOfMonth(), nthOfMonth()
// firstOfQuarter(), lastOfQuarter(), nthOfQuarter()
// firstOfYear(), lastOfYear(), nthOfYear()

常量

Carbon 中定义了以下常量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// These getters specifically return integers, ie intval()
var_dump(Carbon::SUNDAY); // int(0)
var_dump(Carbon::MONDAY); // int(1)
var_dump(Carbon::TUESDAY); // int(2)
var_dump(Carbon::WEDNESDAY); // int(3)
var_dump(Carbon::THURSDAY); // int(4)
var_dump(Carbon::FRIDAY); // int(5)
var_dump(Carbon::SATURDAY); // int(6)

var_dump(Carbon::YEARS_PER_CENTURY); // int(100)
var_dump(Carbon::YEARS_PER_DECADE); // int(10)
var_dump(Carbon::MONTHS_PER_YEAR); // int(12)
var_dump(Carbon::WEEKS_PER_YEAR); // int(52)
var_dump(Carbon::DAYS_PER_WEEK); // int(7)
var_dump(Carbon::HOURS_PER_DAY); // int(24)
var_dump(Carbon::MINUTES_PER_HOUR); // int(60)
var_dump(Carbon::SECONDS_PER_MINUTE); // int(60)
1
2
3
4
$dt = Carbon::createFromDate(2012, 10, 6);
if ($dt->dayOfWeek === Carbon::SATURDAY) {
echo 'Place bets on Ottawa Senators Winning!';
}

CarbonInterval 类

Carbon 类继承了PHP 的DateInterval类。

1
2
3
4
5
<?php
class CarbonInterval extends \DateInterval
{
// code here
}

你也可以通过一下方法创建一个实例。

1
2
3
4
5
6
echo CarbonInterval::year();                           // 1 year
echo CarbonInterval::months(3); // 3 months
echo CarbonInterval::days(3)->seconds(32); // 3 days 32 seconds
echo CarbonInterval::weeks(3); // 3 weeks
echo CarbonInterval::days(23); // 3 weeks 2 days
echo CarbonInterval::create(2, 0, 5, 1, 1, 2, 7); // 2 years 5 weeks 1 day 1 hour 2 minutes 7 seconds

如果你继承的 DateInterval 实例来自其他类库,别怕,通过 instance() 这种友好的方式你手动可以创建一个 CarbonInterval 实例。

1
2
3
4
$di = new \DateInterval('P1Y2M'); // <== instance from another API
$ci = CarbonInterval::instance($di);
echo get_class($ci); // 'Carbon\CarbonInterval'
echo $ci; // 1 year 2 months

其他的一些辅助方法,但要注意这些辅助方法处理周时,只有天会被保存下来。并且是在当前实例的总天数基础上计算周。

1
2
3
4
5
6
7
echo CarbonInterval::year()->years;                    // 1
echo CarbonInterval::year()->dayz; // 0
echo CarbonInterval::days(24)->dayz; // 24
echo CarbonInterval::days(24)->daysExcludeWeeks; // 3
echo CarbonInterval::weeks(3)->days(14)->weeks; // 2 <-- days setter overwrites the current value
echo CarbonInterval::weeks(3)->weeks; // 3
echo CarbonInterval::minutes(3)->weeksAndDays(2, 5); // 2 weeks 5 days 3 minutes

也有一个方便的 forHumans(),在调用 __toString() 方法时,自动执行,并打印出可读性更好的时间格式。

1
2
3
4
CarbonInterval::setLocale('fr');
echo CarbonInterval::create(2, 1)->forHumans(); // 2 ans 1 mois
echo CarbonInterval::hour()->seconds(3); // 1 heure 3 secondes
CarbonInterval::setLocale('en');

当然,你可以通过 CarbonInterval::setLocale('fr') 来改变语言显示。

[译] dotenv - PHP 版本的 .env

原文地址:PHP dotenv

通过 .env 加载环境变量并且能够自动的通过 getenv(), $_ENV$_SERVER 自动调用.

这是一个 PHP 版本 Ruby dotenv.

为什么是 .env?

你不能在代码中存储任何的敏感/账号数据 存储 在环境中存储配置12-factors 的一项规则. 在部署中可能变化的所有的内容 – 诸如数据库认证或者第三方服务的认证应该从代码中剥离出来.也就是环境变量的概念.

广义上来说, 一个 .env 文件是加载自定义配置的一个简单的方法, 这样你的应用不需要修改 .htaccess 文件或者 Apache/nginx 虚拟主机. 这意味着你不需要编辑项目之外的任何文件,并且所有的环境变量都配置完整了, 无论你运行在 Apache, Nginx, CLI 还是 PHP 5.4 上内建的 web 服务器上. 这是一个所知的设置环境变量最简单的方法, 你会喜欢上它的.

  • 无需在 Apache 或者 Nginx上额外配置虚拟主机配置
  • 无需在 .htaccess 中添加 php_value
  • 方便移植和共享 ENV 环境变量值
  • 兼容服务器内置的服务器和 CLI 运行器

使用 composer 安装

1
2
curl -s http://getcomposer.org/installer | php
php composer.phar require vlucas/phpdotenv

使用方法

.env 文件通常不包含在版本控制内, 它可能包含敏感的 API Key 或者 密码. 所有需要环境变量定义(不敏感的定义)的项目都需要创建一个 .env.example 文件, 这个环境变量包含他们自己定义的环境变量或者联合开发包含的环境变量. 项目合作开发者可以独立的复制 .env.example 并且重命名为 .env , 并且修改为正确的本地环境配置, 存储密码 key 或者提供他们必要的值. 在这个使用方法中 .env 文件应该添加到 .gitignore 文件中并且永远不会被项目的合作者签入/签出. 这个方法确保里边没有敏感的 API Key 或者 密码在版本控制中出现从而减少了安全风险. 并且开发环境中的配置永远不会告知合作开发者.

添加配置到根目录下的 .env 文件, **确保 .env 文件添加到 .gitignore 从而不会签入到 CVS **

1
2
S3_BUCKET=dotenv
SECRET_KEY=souper_seekret_key

现在创建一个 .env.example 文件, 并且签入到项目中. 这里配置和环境变量你需要设置的可以留空或者设置一些无关紧要的数据. 这个方法告知人们这些数据是必须的, 但是不会提供真正环境中的数据.

1
2
S3_BUCKET=devbucket
SECRET_KEY=abc123

你可以使用以下一行代码加载应用中的 .env 文件:

1
Dotenv::load(__DIR__);

所有定义的变量都可以通过 getenv 方法访问到, 并且也可以使用超全局变量$_ENV$_SERVER访问到.

1
2
3
$s3_bucket = getenv('S3_BUCKET');
$s3_bucket = $_ENV['S3_BUCKET'];
$s3_bucket = $_SERVER['S3_BUCKET'];

你同样可以使用框架的 Request 类来访问这些变量(如果你使用框架)

1
2
3
$s3_bucket = $request->env('S3_BUCKET');
$s3_bucket = $request->getEnv('S3_BUCKET');
$s3_bucket = $request->server->get('S3_BUCKET');

嵌入变量

在一个变量中嵌入一个环境变量是可以的, 这样对于减少重复很有用.

使用 {$…} 来包裹环境变量 e.g.

1
2
3
BASE_DIR=/var/webroot/project-root
CACHE_DIR={$BASE_DIR}/cache
TMP_DIR={$BASE_DIR}/tmp

不可变

默认来说, Dotenv 认为环境变量是不变的. 这就是说一旦设置就不能变更.

你可以用以下函数将环境变量设置为可变的

1
Dotenv::makeMutable();

… 同样你也可以使用以下函数让其不再可变

1
Dotenv::makeImmutable();

要求变量必须设置

使用 Dotenv, 你可以指定这个 ENV 变量必须设置, 如没有设置则会抛出异常. 这对于人们是非常有用的, 如果程序缺少这个变量就不能运行.

使用以下语法:

1
Dotenv::required('DATABASE_DSN');

或者数组来定义:

1
Dotenv::required(array('DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS'));

如果 ENV 变量缺少, Dotenv 将抛出一个 RuntimeException :

1
Required environment variable missing or value not allowed: 'DB_USER', 'DB_PASS'

允许的值

你可能看到了上边的异常信息, 你可以设定一个可能范围值, 让你的环境变量遵守这个规则

1
Dotenv::required('SESSION_STORE', array('Filesystem', 'Memcached'));

同样的, 如果环境变量不在这个列表里, 你会收到一个相似的异常信息:

1
Required environment variable missing or value not allowed: 'SESSION_STORE'

注释

可以使用 # 来注释字符. E.g.

1
2
3
# this is a comment
VAR="value" # comment
VAR=value # comment

使用注释

当一个开发者克隆你的代码库. 他们会收到一个额外的手册 一次性步骤 来手动的复制 .env.example 并且重命名为 .env 并且追加上他们自己的值(或者从其他开发者哪里获取到其他敏感值).

phpdotenv 被用来建立开发者环境但是不应该用在生产环境下. 在生产环境下, 需要设置真实的变量而不必每次使用请求的时候都使用 .env 文件进行重载.

这个可以通过自动化部署工具来实现, 例如: Vagrant, chef, Puppet, 或者手动的通过云主机来实现, 例如: Pagodabox, Heroku.

贡献

  1. Fork it
  2. 创建分支(git checkout -b my-new-feature)
  3. 改动
  4. 运行测试, 如果需要, 添加新配置 (phpunit)
  5. 提交 (git commit -am 'Added some feature')
  6. 推送到分支 (git push origin my-new-feature)
  7. 创建新的拉取请求

[转] PHP 生成 PDF 完美支持中文,解决 TCPDF 乱码

原文地址: PHP 生成 PDF 完美支持中文,解决 TCPDF 乱码

PHP 生成 PDF 格式文件以 TCPDF 为基础,TCPDF 是一个用于快速生成 PDF 文件的 PHP5 函数包。TCPDF 基于 FPDF 进行扩展和改进。支持 UTF-8,Unicode,HTML 和  XHTML。在基于 PHP 开发的 Web 应用中,使用它来输出 PDF 文件是绝佳的选择。但毕竟这款开源软件是外国人开发的,对中文的支持也不是那么尽如人   意,因此我们需要对它作进一步的强化。

首先要到 TCPDF 官网下载 TCPDF 最新版。访问 http://www.tcpdf.org ,单击导航条上的”Download”链接,即可下载到最新版本的 TCPDF 压缩包。因为里面包含了许多的 TCPDF 范例和字体文件,因此下载的文件比较大,大概有 10M 左右。下载完后解压它,会得到一个名为 tcpdf 的目录,此目录的结构大概是这样的:

打开 tcpdf 目   录下的 examples 目录,下面有 50 多个范例文件,其中 example_038.php 就是用来测试东亚字体的(比如简体/繁体中文、日文等等),打开此文件,找到$pdf->SetFont  这一行,修改为:

1
$pdf->SetFont(‘stsongstdlight’, ”, 20);

这一行代码的作用是设置 PDF 正文所用的字体及字号。其中”stsongstdlight”表示”STSongStdLight”字体,这是 Adobe Reader 的默认简体中文字体,TCPDF 中已经内置这个字体的配置文件,我们只需直接调用即可。接下来,

1
2
3
$pdf->Write(0,’敏捷的棕毛狐狸跃过那只懒狗‘, ”, 0, ’L', true, 0, false, false, 0);
$pdf->Write(0,’The quick brown fox jumps over the lazy dog.’, ”, 0, ’L', true, 0, false, false, 0);
$pdf->Write(0,’1234567890′, ”, 0, ’L', true, 0, false, false, 0);

保存,然后访问  http://localhost/tcpdf/examples/example_038.php  就可以生成一份 PDF 文档了:

这种方式生成的 PDF 文件的优点是:文件体积小,生成快速。但也有缺点是,没有嵌入中文字体,只限于安装了 Adobe Reader 之后才能正常显示。那万一用户使用的是 FoxIt Reader 或者是 Linux 操作系统呢?显示效果就不一样了。因此,为了保证生成的 PDF 文件在任何环境下都有同样的显示效果,嵌入字体是必需的。

Windows 下有很多中文字体,但是我们要用在 TCPDF 中的中文字体有下面几个要求:

  • 支持 Unicode,因为 TCPDF 支持的是 Unicode;
  • 体积越小越好;
  • 最好是也支持繁体中文;

然而 TCPDF 不支持 TTF 字体文件,因此我们先将它转换成 TCPDF 支持的格式,然后再使用。在 TCPDF 目录下有个 fonts 子目录,这个子目录是存放转换后的字体, tools 中有个转换工具, 可以转换字体, 下面是转换的步骤:

我们把下载到的  simhei.ttf  复制到  TCPDF\tools  下面,然后打开命令行,切换到此路径下,输入如下命令:

1
2
3
4
5
6
php ./tcpdf_addfont.php -b -t TrueTypeUnicode -i simhei.ttf

>>> Converting fonts for TCPDF:
*** Output dir set to ../tecnickcom/tcpdf/fonts/
+++ OK : ~/tecnickcom/tcpdf/tools/simhei.ttf added as simhei
>>> Process successfully completed!

之后,可以发现此目录下生成了simhei.ctg.z,simhei.php,simhei.z  这三个文件。

生成不成功也没问题,另一个方法是到 Joomla 中文官网http://www.joomlagate.com下载Joomla中文程序找到\language\pdf_fonts目录下复制droidsansfallback.php、droidsansfallback.z以及droidsansfallback.ctg.z这三个文件也是可以的

打开 example_038.php 文件,将

1
2
$pdf->SetFont(‘stsongstdlight’, ”, 20);
$pdf->SetFont(‘simhei’, ”, 20);

这样就能够调用我们刚才生成的字体

[转+] Laravel API 结合 Dingo API 和 JWT

原文地址: Laravel API结合Dingo API和JWT

介绍

关于API的开发 不得不提的就是可以利用Dingo来构建更加强大的API 这样我们可以更好的去实现API认证和请求;
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。

安装插件

安装 dinggo Api

首先当然是去安装页面 根据提供的包进行下载 在laravel项目中就是require这个package, 然后 composer update -vvv

1
"dingo/api": "1.0.*@dev"

接着在laravel项目的configapp.php去添加 ServiceProvider

1
2
3
'providers' => [
Dingo\Api\Provider\LaravelServiceProvider::class
]

再去生成相应的配置文件

1
$ php artisan vendor:publish --provider="Dingo\Api\Provider\LaravelServiceProvider"

安装 Jwt

如果需要实现jwt 同样的也是去安装页面 安, 配置 composer.json 文件, 然后运行 composer update -vvv
在整理这篇文章的时候, 1.0 已经在 rc 版本, 快要发布了. 而 0.5 版本中有些不兼容的问题, 故而这里采用的是 1.0 版本, 引入的 ServiceProvider 有所不同, 在安装以及设置上需要注意

1
"tymon/jwt-auth": "1.0.*@dev",

添加对应的服务:

1
Tymon\JWTAuth\Providers\LaravelServiceProvider::class

配置的 alias

1
2
'JWTAuth'    => Tymon\JWTAuth\Facades\JWTAuth::class,
'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,

生成配置文件

1
$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

生成 jwt key

1
$ php artisan jwt:secret

使用

配置 Dinggo Api

这个时候我们是在开发的环境下 还需对Dingo进行相应的配置 在.env文件里

1
2
3
4
5
6
7
8
9
10
11
# 类型
API_STANDARDS_TREE=vnd

# 前缀
API_PREFIX=api

# 版本
API_VERSION=v1

# 开启 Debug 模式
API_DEBUG=true

配置认证

我们可以实现一个 jwtauth 认证 在 config/api.php 里配置

1
2
3
4
5
6
7
8
'auth' => [
'basic'=>function($app){
return new Dingo\Api\Auth\Provider\Basic($app['auth']);
},
'jwt'=>function($app){
return new Dingo\Api\Auth\Provider\JWT($app['Tymon\JWTAuth\JWTAuth']);
}
],

这样我们就实现了在Dingojwt认证

注册中间件

既然是auth认证我们就需要先注册刚配置好的认证 即在Kernel文件里添加

1
2
'jwt.auth'        => 'Tymon\JWTAuth\Http\Middleware\Check',
'jwt.refresh' => 'Tymon\JWTAuth\Http\Middleware\RefreshToken',

添加路由

laravel 5.2以后的版本我们可以直接放在 routes/api.php

1
$api = app('Dingo\Api\Routing\Router');

为了区分 我们可以在app目录下新建Api目录, 然后再新建Controllers和在Http目录一样 在这里用来管理api的控制器

在这个目录下新建一个基本的控制器 BaseController

1
2
3
4
5
6
7
8
<?php
namespace App\Api\Controllers;
use App\Http\Controllers\Controller;
use Dingo\Api\Routing\Helpers;
class BaseController extends Controller
{
use Helpers;
}

此时我们再去创建对数据的api时就可以继承这个控制器并可以使用 Dingo ApiHelpers 函数.

比如在此目录下创建PostsController

这样我们就可以在 routes.php 里根据 Dingo 提供的方法去定义api

1
2
3
4
5
6
7
8
9
10
11
$api = app('Dingo\Api\Routing\Router');

$api->version('v1', function ($api) {
$api->group([
'namespace' => 'App\Api\Controllers'
], function ($api) {
$api->get('lessons','PostsController@index');
$api->get('lessons/{id}','PostsController@show');
});
});

PostsControllerindex返回所有数据 那么再去访问http://localhost:8000/api/lessons 就可以看到所有的数据了

在这里使用 dinggo 的方法路由, 这和框架定义路由方式不太一样

当然和之前的一样 我们需要对数据字段进行映射 那么我们可以在Api目录下新建Transformer目录 然后在这个目录下新建PostTransformer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
namespace App\Api\Transformer;
use App\Post;
use League\Fractal\TransformerAbstract;
class PostTransformer extends TransformerAbstract
{
public function transform(Post $post)
{
return [
'title' => $post['title'],
'content' => $post['body'],
'is_free' => (boolean)$ppost['free']
];
}
}

在这里我们是可以使用Dingo APITransformerTransformerAbstract

这样写完我们就可以在控制器里去重新返回所有信息

1
2
3
4
5
 public function index()
{
$lessons = Post::all();
return $this->collection($post,new PostTransformer());
}

这里的PostTransformer是 App\Api\Transformer\PostTransformer

当然还有之前的show方法 因为他的返回状态信息之前都是自己写的 其实在Dingo里也有相应的方法

1
2
3
4
5
6
7
8
public function show($id)
{
$lesson = Lesson::find($id);
if(! $lesson){
return $this->response->errorNotFound('Lesson not found');
}
return $this->item($lesson,new LessonTransformer());
}

结合Jwt的auth认证

Auth 认证的前提

User 模型需要实现 Tymon\JWTAuth\Contracts\JWTSubject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use Tymon\JWTAuth\Contracts\JWTSubject as JWTSubjectAuthenticatable;

class User extends Model implements JWTSubjectAuthenticatable
{
use Authenticatable;

...

/**
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey(); // Eloquent model method
}

/**
* @return array
*/
public function getJWTCustomClaims()
{
return [
'user' => [
'id' => $this->id,
...
]
];
}
}

Jwt 认证

App\Api\Controllers目录下新建AuthController并继承之前定义好的BaseController

jwt创建token的页面 我们就可以使用它的authenticate方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function authenticate(Request $request)
{
// grab credentials from the request
$credentials = $request->only('email','password');
try {
// attempt to verify the credentials and create a token for the user
if (! $token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
// something went wrong whilst attempting to encode the token
return response()->json(['error' => 'could_not_create_token'], 500);
}
// all good so return the token
return response()->json(compact('token'));
}

为了执行这个方法 可以去路由中定义

1
2
3
4
5
6
$api->version('v1', function ($api) {
$api->group(['namespace' => 'App\Api\Controllers'], function ($api) {
$api->post('user/login','AuthController@authenticate');
$api->post('user/register','AuthController@register');
});
});

这个时候再去查看一下我们的路由的话就会看到新定义的post路由

为了验证请求的结果 我们可以使用postman这个chrome工具 去请求http://localhost:8000/api/user/login

这个时候是会返回{"error":"invalid_credentials"}

为了能够正确通过我们可以在body部分给出用户邮箱和密码(用户可用thinker创建一个) 这个时候就会正确返回一个token

这个token就是用来保护有jwt认证下的信息

添加 jwt 认证限制

我们可以为Post的数据添加一个middleware

1
2
3
4
$api->group(['middleware'=>'jwt.auth'],function ($api){
$api->get('posts',PostsController@index');
$api->get('posts/{id}','PostsController@show');
});

所以这个时候如果我们没有之前authenticate返回的token的话 我们是无法访问api/postsapi/post/{id}

只有加上返回的token我们才能继续访问到之前的数据信息 如/api/posts?token=xxxxxx

既然只有登录的用户才能访问到这些资源 那么我们是不是也可以去拿到登录的用户

jwtAuthentication里就提供了getAuthenticatedUser这个方法 所以为了查看效果 可以去注册一条路由

1
2
3
$api->group(['middleware'=>'jwt.auth'],function ($api){
$api->get('user/me','AuthController@getAuthenticatedUser');
});

接着在AuthController里去定义这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function getAuthenticatedUser()
{
try {
if (! $user = JWTAuth::parseToken()->authenticate()) {
return response()->json(['user_not_found'], 404);
}
} catch (TokenExpiredException $e) {
return response()->json(['token_expired'], $e->getStatusCode());
} catch (TokenInvalidException $e) {
return response()->json(['token_invalid'], $e->getStatusCode());
} catch (JWTException $e) {
return response()->json(['token_absent'], $e->getStatusCode());
}
// the token is valid and we have found the user via the sub claim
return response()->json(compact('user'));
}

所以说这时候去访问http://localhost:8000/api/user/me?token=xxx就可以拿到当前登录的用户信息了

相关链接

源码阅读 - 初始 : (1) 入口文件 index

入口文件 public/index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1) Composer 自动加载: 无需关心类库是如何被引入的
require __DIR__.'/../bootstrap/autoload.php';
// -- 定义 LARAVEL_START
// -- 加载 vendor/autoload.php

// 2) 加载App入口文件并初始化
$app = require_once __DIR__.'/../bootstrap/app.php';

// 3) 创建 kernel
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

// 4) 处理请求
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

// 5) 发送响应
$response->send();

// 6) 终止
$kernel->terminate($request, $response);

1) 自动加载

bootstrap/autoload.php

1
2
3
4
5
// 定义 LARAVEL_START
define('LARAVEL_START', microtime(true));

// 1) 加载 bootstrap 下的自动加载
require __DIR__.'/../vendor/autoload.php';

bootstrap 下的 自动加载
~/vendor/autoload.php

1
2
3
4
5
6
7
8
9
10
11
12
# 加载指定定义文件
vendor/composer/autoload_real.php

# 调用 loader
ComposerAutoloaderInitXX::getLoader();
# ? autoload_static.php
# : autoload_namespaces.php # 命名空间
autoload_psr4.php # 自行加载
autoload_classmap.php # 类的映射

# 加载文件
# autoload_files.php

2) 应用程序加载

bootstrap/app.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 1) 初始化 app
$app = new Illuminate\Foundation\Application(
realpath(__DIR__ . '/../')
);


/* 2) 绑定重要接口, 来源请求来自于 web 和 CLI
* 将两个类绑定, 进行自动加载的时候进行存储对象的获取
-------------------------------------------- */

// Http 核心
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);

// 命令行核心
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);

// 异常处理
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);

// 返回应用实例
return $app;

3) 生成加载器

1
2
// 创建 Kernel
$app->make(Illuminate\Contracts\Http\Kernel::class)
1
2
3
4
5
6
7
8
9
10
11
// 获取引用
$abstract = $this->getAlias($abstract);

// 未加载则进行加载
if (isset($this->deferredServices[$abstract]) && ! isset($this->instances[$abstract])) {
$this->loadDeferredProvider($abstract);
# 注册 provider
}

// 返回实例化的对象
return parent::make($abstract, $parameters);

4) 处理请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
try {
// 启用 Http 方法重写
// 支持 在 post 方法下, 使用 _method 替代 PUT, DELETE 等方法
$request->enableHttpMethodParameterOverride();

// 1) 通过路由发送请求
$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {

// 2) 异常处理和渲染
$this->reportException($e);

$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));

$response = $this->renderException($request, $e);
}

// 3) 触发请求处理事件
$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);

return $response;

5) 发送响应

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1) 发送 Header
$this->sendHeaders();

// 2) 发送内容
$this->sendContent();

if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif ('cli' !== PHP_SAPI) {
static::closeOutputBuffers(0, true);
}

return $this;

6) 终止程序

1
2
3
4
5
// 1) 终止中间件
$this->terminateMiddleware($request, $response);

// 2) 终止 App
$this->app->terminate();

laravel 生命周期

laravel 的生命周期主要分为 3 个主要阶段:

  • 加载项目依赖,
  • 创建应用实例,
  • 接受请求并响应

入口文件实现的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
// 一
require __DIR__ . '/../vendor/autoload.php';

//二
$app = require_once __DIR__ . '/../bootstrap/app.php';

//三
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

$response->send();

$kernel->terminate($request, $response);

生命周期

1.加载项目依赖

php 依赖于 composer 包管理,通过引入由 composer 包管理器自动生成的类加载程序,进行注册并加载第三方组件.

1
2
<?php
require __DIR__ . '/../vendor/autoload.php';

2.创建应用实例

1
2
创建应用实例/服务容器,执行代码位于bootstrap/app.php文件,创建应用实例过程包括:注册项目基础服务、注册项目服务提供者别名、注册目录路径等等一系列操作
如下为app.php的代码,主要完成创建应用实例和绑定核心内容到服务容器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php

/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
*/

$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);

/*
|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
*/

$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);

$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);

$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);

/*
|--------------------------------------------------------------------------
| Return The Application
|--------------------------------------------------------------------------
*/

return $app;

2.1 创建应用实例

即对 Illuminate\Foundation\Application 进行实例化,称之为 app 容器。在实例化 app 容器的时候,完成以下几个工作:

  • 注册应用基础路径并绑定到 app 服务容器;
  • 注册基础服务提供者至 app 服务容器;
  • 注册核心容器别名到 app 服务容器;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Create a new Illuminate application instance.
*
* @param string|null $basePath
* @return void
*/
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}

$this->registerBaseBindings();

$this->registerBaseServiceProviders();

$this->registerCoreContainerAliases();
}

2.2 绑定内核

1
2
3
laravel根据http请求的环境,将请求分别发送至相应的http内核(Http\Kernel::class)和console内核(Console\Kernel::class),
无论 HTTP内核还是 Console内核,它们都是接收一个 HTTP请求,随后返回一个响应。
在http内核中定义了【中间件】相关数组,在Illuminate\Foundation\Http\Kernel类中,定义了属性 $bootstrappers的引导程序数组
  • 中间件 : 提供了一种方便的机制来过滤进入应用的 HTTP 请求。
  • 引导程序:包括完成环境检测、配置加载、异常处理、Facades 注册、服务提供者注册、启动服务这六个引导程序。
1
2
3
4
5
6
7
8
protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];

2.3 注册异常处理

3.接收请求并响应

1
2
3
4
5
6
7
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

$response->send();

3.1 解析内核程序

第二阶段已经将 http 内核和 console 内核绑定到了 app 服务容器,使用 app 服务容器的 make 方法,将内核解析出来

1
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
  • 内核实例化的时候又进行了哪些操作呢,进一步查看 Illuminate\Foundation\Http\Kernel 内核的
    __construct(Illuminate\Contracts\Foundation\Application $app, \Illuminate\Routing\Router $router)构造方法,
  • 它接收 APP 容器和路由器两个参数:
    在实例化内核时,构造函数内将在 HTTP 内核定义的「中间件组」注册到 路由器,注册完后就可以在实际处理 HTTP 请求前
    调用这些「中间件」实现 过滤 请求的目的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
/**
* Create a new HTTP kernel instance. 创建 HTTP 内核实例
*
* @class Illuminate\Foundation\Http\Kernel
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Routing\Router $router
* @return void
*/
public function __construct(Application $app, Router $router)
{
$this->app = $app;
$this->router = $router;

$router->middlewarePriority = $this->middlewarePriority;

foreach ($this->middlewareGroups as $key => $middleware) {
$router->middlewareGroup($key, $middleware);
}

foreach ($this->routeMiddleware as $key => $middleware) {
$router->aliasMiddleware($key, $middleware);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
/**
* Register a group of middleware. 注册中间件组
*
* @class \Illuminate\Routing\Router
* @param string $name
* @param array $middleware
* @return $this
*/
public function middlewareGroup($name, array $middleware)
{
$this->middlewareGroups[$name] = $middleware;

return $this;
}

/**
* Register a short-hand name for a middleware. 注册中间件别名
*
* @class \Illuminate\Routing\Router
* @param string $name
* @param string $class
* @return $this
*/
public function aliasMiddleware($name, $class)
{
$this->middleware[$name] = $class;

return $this;
}

3.2 处理 http 请求

1
之前的所有处理,基本都是围绕在配置变量、注册服务等运行环境的构建上,构建完成后才开始处理一个「HTTP 请求」。

处理请求包含两个阶段:

  • 创建请求实例
  • 处理请求
1
2
3
4
5
6
<?php
// 处理请求
$response = $kernel->handle(
// 创建请求实例
$request = Illuminate\Http\Request::capture()
);

3.2.1 创建请求实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* Create a new Illuminate HTTP request from server variables.
*
* @class Illuminate\Http\Request
* @return static
*/
public static function capture()
{
static::enableHttpMethodParameterOverride();
return static::createFromBase(SymfonyRequest::createFromGlobals());
}

/**
* Create an Illuminate request from a Symfony instance.
*
* @see https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpFoundation/Request.php
* @param \Symfony\Component\HttpFoundation\Request $request
* @return \Illuminate\Http\Request
*/
public static function createFromBase(SymfonyRequest $request)
{
if ($request instanceof static) {
return $request;
}

$content = $request->content;

$request = (new static)->duplicate(
$request->query->all(), $request->request->all(), $request->attributes->all(),
$request->cookies->all(), $request->files->all(), $request->server->all()
);

$request->content = $content;

$request->request = $request->getInputSource();

return $request;
}

3.2.2 处理请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Handle an incoming HTTP request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();

$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
$this->reportException($e);

$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));

$response = $this->renderException($request, $e);
}

$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);

return $response;
}

handle() 方法接收一个 HTTP 请求,并最终生成一个 HTTP 响应。

继续深入到处理 HTTP 请求的方法 内部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);

Facade::clearResolvedInstance('request');

$this->bootstrap();

return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}

这段代码完成了如下操作:

  • 将 $request 实例注册到 APP 容器 供后续使用;

  • 清除之前 $request 实例缓存;

  • 启动「引导程序」;

  • 发送请求至路由。

    3.2.2.1 启动引导程序

上述$this->bootstrap();进行了引导程序的启动,调用了 app 容器的 bootstrapWith();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* The bootstrap classes for the application. 应用的引导程序
*
* @var array
*/
protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];

/**
* Bootstrap the application for HTTP requests.
*
* @class Illuminate\Foundation\Http\Kernel
* @return void
*/
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}

/**
* Get the bootstrap classes for the application.
*
* @return array
*/
protected function bootstrappers()
{
return $this->bootstrappers;
}

/**
* Get the bootstrap classes for the application.
*
* @return array
*/
protected function bootstrappers()
{
return $this->bootstrappers;
}

看一下 Illuminate\Foundation\Application 的 bootstrapWith()方法是如何来启动这些引导程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Run the given array of bootstrap classes.
*
* @class Illuminate\Foundation\Application
* @param array $bootstrappers
* @return void
*/
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;

foreach ($bootstrappers as $bootstrapper) {
$this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);

$this->make($bootstrapper)->bootstrap($this);

$this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
}
}

我们看到在 APP 容器内,会先解析对应的「引导程序」,随后调用「引导程序」的 bootstrap() 完成的「引导程序」的启动操作。

  • 引导程序列表:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
protected $bootstrappers = [
/*
|--------------------------------------------------------------------------
| 环境检测,将.env配置信息载入到$_ENV变量中
|--------------------------------------------------------------------------
|Detect if a custom environment file matching the APP_ENV exists,
*/
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,

/*
|--------------------------------------------------------------------------
| 加载配置文件
|--------------------------------------------------------------------------
|Load the configuration items from all of the files,
*/
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,

/*
|--------------------------------------------------------------------------
| 异常处理
|--------------------------------------------------------------------------
|Convert PHP errors to ErrorException instances
*/
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,

/*
|--------------------------------------------------------------------------
| 注册Facades
|--------------------------------------------------------------------------
|注册完成后可以以别名的方式访问具体的类
*/
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,

/*
|--------------------------------------------------------------------------
| 注册服务提供者
|--------------------------------------------------------------------------
|在「创建应用实例」已经将基础服务提供者注册到APP容器。在这里会将配置在 app.php文件夹下
|providers节点的服务器提供者注册到 APP 容器,供请求处理阶段使用;
*/
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,

//启动服务
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];

我们选取 Illuminate\Foundation\Bootstrap\LoadConfiguration::class,来查看一下启动的原理,它的功能是加载配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
$items = [];

if (file_exists($cached = $app->getCachedConfigPath())) {
$items = require $cached;

$loadedFromCache = true;
}

$app->instance('config', $config = new Repository($items));

if (! isset($loadedFromCache)) {
$this->loadConfigurationFiles($app, $config);
}

$app->detectEnvironment(function () use ($config) {
return $config->get('app.env', 'production');
});

date_default_timezone_set($config->get('app.timezone', 'UTC'));

mb_internal_encoding('UTF-8');
}

/**
* Load the configuration items from all of the files.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Contracts\Config\Repository $repository
* @return void
* @throws \Exception
*/
protected function loadConfigurationFiles(Application $app, RepositoryContract $repository)
{
$files = $this->getConfigurationFiles($app);

if (! isset($files['app'])) {
throw new Exception('Unable to load the "app" configuration file.');
}

foreach ($files as $key => $path) {
$repository->set($key, require $path);
}
}

「创建 Laravel 应用实例」的时候执行了一步「注册应用的基础路径并将路径绑定到 APP 容器」的操作。
当前,LoadConfiguration 类就是将 config 目录下的所有配置文件读取到一个集合中,这样我们就可以使用 config()辅助函数获取配置数据

  • 以下为 config()助手函数的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if (! function_exists('config')) {
/**
* Get / set the specified configuration value.
*
* If an array is passed as the key, we will assume you want to set an array of values.
*
* @param array|string $key
* @param mixed $default
* @return mixed|\Illuminate\Config\Repository
*/
function config($key = null, $default = null)
{
if (is_null($key)) {
return app('config');
}

if (is_array($key)) {
return app('config')->set($key);
}

return app('config')->get($key, $default);
}
}

3.2.2.2 发送请求至路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);

Facade::clearResolvedInstance('request');

$this->bootstrap();

return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}

在 「发送请求至路由」这行代码中,完成了四个不同的操作:

  • 管道(pipeline)创建
  • 将 $request 传入管道
  • 对 $request 执行「中间件」处理
  • 实际的请求处理
    先来看看$this->dispatchToRouter() 这个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Get the route dispatcher callback. 获取一个路由分发器匿名函数
*
* @return \Closure
*/
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);

return $this->router->dispatch($request);
};
}

在进行「解析内核实例」的时候已经将 Illuminate\Routing\Router 对象赋值给$this->router 属性;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<?php
class Router implements RegistrarContract, BindingRegistrar
{
use Macroable {
__call as macroCall;
}

/**
* Dispatch the request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
public function dispatch(Request $request)
{
$this->currentRequest = $request;

return $this->dispatchToRoute($request);
}

/**
* Dispatch the request to a route and return the response. 将请求分发到路由并返回响应
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function dispatchToRoute(Request $request)
{
return $this->runRoute($request, $this->findRoute($request));
}

/**
* Find the route matching a given request. 查找对应的路由实例
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Routing\Route
*/
protected function findRoute($request)
{
$this->current = $route = $this->routes->match($request);

$this->container->instance(Route::class, $route);

return $route;
}

/**
* Return the response for the given route. 运行给定的路由
*
* @param Route $route
* @param Request $request
* @return mixed
*/
protected function runRoute(Request $request, Route $route)
{
$request->setRouteResolver(function () use ($route) {
return $route;
});

$this->events->dispatch(new Events\RouteMatched($route, $request));

return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}

/**
* Run the given route within a Stack "onion" instance. 通过一个实例栈运行给定的路由
*
* @param \Illuminate\Routing\Route $route
* @param \Illuminate\Http\Request $request
* @return mixed
*/
protected function runRouteWithinStack(Route $route, Request $request)
{
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;

$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);

return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}
}

执行$route->run()的方法定义在 Illuminate\Routing\Route 类中,最终执行在「routes/web.php 配置的匹配到的控制器或匿名函数」:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Run the route action and return the response. 执行路由方法并返回响应
*
* @return mixed
*/
public function run()
{
$this->container = $this->container ?: new Container;

try {
if ($this->isControllerAction()) {
return $this->runController();
}

return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}

其执行结果会通过 Illuminate\Routing\Router::prepareResponse($request, $response)生一个响应实例并返回。
至此,Laravel 就完成了一个 HTTP 请求的请求处理。

4.发送响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
// 一
require __DIR__ . '/../vendor/autoload.php';

//二
$app = require_once __DIR__ . '/../bootstrap/app.php';

//三
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

$response->send();

$kernel->terminate($request, $response);

发送响应由 Illuminate\Http\Response 父类 Symfony\Component\HttpFoundation\Response 中的 send() 方法完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Sends HTTP headers and content.
*
* @return $this
*/
public function send()
{
//发送响应头
$this->sendHeaders();

//发送报文主题
$this->sendContent();

if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (!\in_array(PHP_SAPI, array('cli', 'phpdbg'), true)) {
static::closeOutputBuffers(0, true);
}

return $this;
}

5.程序终止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* Call the terminate method on any terminable middleware.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return void
*/
public function terminate($request, $response)
{
$this->terminateMiddleware($request, $response);

$this->app->terminate();
}

/**
* Call the terminate method on any terminable middleware.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return void
*/
protected function terminateMiddleware($request, $response)
{
$middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
$this->gatherRouteMiddleware($request),
$this->middleware
);

foreach ($middlewares as $middleware) {
if (! is_string($middleware)) {
continue;
}

list($name) = $this->parseMiddleware($middleware);

$instance = $this->app->make($name);

if (method_exists($instance, 'terminate')) {
$instance->terminate($request, $response);
}
}
}

以上便是 Laravel 的请求生命周期的始末。

[译+]fzaninotto_Faker 文档

原文地址: Faker

Formatters 格式化器

Faker 可以通过访问您想要的数据类型的属性来生成数据

每个生成器属性 (例如 name, address, lorem) 都被称谓 “formatters”. 一个 faker 生成器有很多, 打包在 Provider 中。下面是默认区域中绑定的格式化程序的列表。

Base 基本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//生成随机整数 0 - 9
$randomDigit = $this->faker()->randomDigit; // 0

//生成随机不为空的整数
$randomDigitNotNull = $this->faker()->randomDigitNotNull; // 8

//生成随机数字
$randomNumber = $this->faker()->randomNumber($nbDigits = NULL, $strict = false); // 3487065

//生成随机浮点数
$randomFloat = $this->faker()->randomFloat($nbMaxDecimals = NULL, $min = 0, $max = NULL); // 45.013726488

//在指定范围内生成随机数
$numberBetween = $this->faker()->numberBetween($min = 1000, $max = 9000); // 1027

//生成随机字符
$randomLetter = $this->faker()->randomLetter; // k

//在给定的数组中,随机生成给定的个数字符
$randomElements = $this->faker()->randomElements($array = array('a', 'b', 'c'), $count = 2); // array('c', 'a')

//在给定的数组中,生成单个随机字符
$randomElement = $this->faker()->randomElement($array = array('a', 'b', 'c')); // "b"

//打乱给定的字符串
$shuffleStr = $this->faker()->shuffle('hello, world'); // 'rlo,h eoldlw'

//打乱给定的数组
$shuffleArr = $this->faker()->shuffle(array(1, 2, 3)); // array(2, 1, 3)

//给占位符生成随机整数 (数字为#)
$numerify = $this->faker()->numerify('Hello ###'); // 'Hello 609'

//给占位符生成随机字符串 (字符串为?)
$lexify = $this->faker()->lexify('Hello ???'); // 'Hello wgt'

//给占位符生成混合的随机字符串
$bothify = $this->faker()->bothify('Hello ##??'); // 'Hello 42jz'

//给占位符生成随机的字符(字母、数字、符号)
$asciify = $this->faker()->asciify('Hello ***'); // 'Hello R6+'

//根据正则规则生成随机字符
$regexify = $this->faker()->regexify('[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'); // "NXF@QJ87.XDR"

Lorem 文本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//生成随机个数的字符串
$word = $this->faker()->word; // 'aut'

//随机生成指定个数的字符串
$words = $this->faker()->words($nb = 3, $asText = false); // array('porro', 'sed', 'magni')

//随机生成一条语句
$sentence = $this->faker()->sentence($nbWords = 6, $variableNbWords = true); // 'Sit vitae voluptas sint non voluptates.'

//随机生成指定条数的语句
//参数 $asText是否作为文本, false:为数组,true:为一个字符串
$sentences = $this->faker()->sentences($nb = 3, $asText = false); // array('Optio quos qui illo error.', 'Laborum vero a officia id corporis.', 'Saepe provident esse hic eligendi.')

//随机生成一个段落
$paragraph = $this->faker()->paragraph($nbSentences = 3, $variableNbSentences = true); // 'Ut ab voluptas sed a nam. Sint autem inventore aut officia aut aut blanditiis. Ducimus eos odit amet et est ut eum.'

//随机生成指定个数段落
$paragraphs = $this->faker()->paragraphs($nb = 3, $asText = false); // array('Quidem ut sunt et quidem est accusamus aut. Fuga est placeat rerum ut. Enim ex eveniet facere sunt.', 'Aut nam et eum architecto fugit repellendus illo. Qui ex esse veritatis.', 'Possimus omnis aut incidunt sunt. Asperiores incidunt iure sequi cum culpa rem. Rerum exercitationem est rem.')

//随机生成一个文本
$text = $this->faker()->text($maxNbChars = 200); // 'Fuga totam reiciendis qui architecto fugiat nemo. Consequatur recusandae qui cupiditate eos quod.'

Person 人物

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//随机生成任务称呼Mrs./Mr./Dr. ...
$title = $this->faker()->title($gender = null | 'male' | 'female'); // 'Ms.'

//随机生成男性称呼
$titleMale = $this->faker()->titleMale; // 'Mr.'

//随机生成女性称呼
$titleFemale = $this->faker()->titleFemale; // 'Ms.'

//随机生成后缀
$suffix = $this->faker()->suffix; // 'Jr.'

//随机生成姓名
$name = $this->faker()->name($gender = null | 'male' | 'female'); // 'Dr. Zane Stroman'

//随机生成名字
$firstName = $this->faker()->firstName($gender = null | 'male' | 'female'); // 'Maynard'

//随机生成男性 名字
$firstNameMale = $this->faker()->firstNameMale; // 'Maynard'

//随机生成女性 名字
$firstNameFemale = $this->faker()->firstNameFemale; // 'Rachel'

//随机生成姓氏
$lastName = $this->faker()->lastName; // 'Zulauf'

Address 地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//随机生成城市前缀town/port/haven...
$cityPrefix = $this->faker()->cityPrefix; // 'Lake'

//
$secondaryAddress = $this->faker()->secondaryAddress; // 'Suite 961'

//随机生成省份/州
$state = $this->faker()->state; // 'NewMexico'

//随机城市省份/州缩写
$stateAbbr = $this->faker()->stateAbbr; // 'OH'

//随机生成城市后缀
$citySuffix = $this->faker()->citySuffix; // 'borough'

//随机生成街道后缀
$streetSuffix = $this->faker()->streetSuffix; // 'Keys'

//随机生成建筑编号
$buildingNumber = $this->faker()->buildingNumber; // '484'

//随机生成城市
$city = $this->faker()->city; // 'West Judge'

//随机生成街道名
$streetName = $this->faker()->streetName; // 'Keegan Trail'

//随机生成街道地址
$streetAddress = $this->faker()->streetAddress; // '439 Karley Loaf Suite 897'

//随机生成邮编
$postcode = $this->faker()->postcode; // '17916'

//随机生成地址
$address = $this->faker()->address; // '8888 Cummings Vista Apt. 101, Susanbury, NY 95473'

//随机生成国家
$country = $this->faker()->country; // 'Falkland Islands (Malvinas)'

//随机生成纬度
$latitude = $this->faker()->latitude($min = -90, $max = 90); // 77.147489

//随机生成经度
$longitude = $this->faker()->longitude($min = -180, $max = 180); // 86.211205

PhoneNumber 电话号码

1
2
3
4
5
6
7
8
//生成随机电话号码
$phoneNumber = $this->faker()->phoneNumber; // "(757) 833-3219 x158"

//随机生成免费电话号码
$tollFreePhoneNumber = $this->faker()->tollFreePhoneNumber; // "888-205-1163"

//随机生成e164电话
$e164PhoneNumber = $this->faker()->e164PhoneNumber; // "+8283952638578"