这些设置对原始查询 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 变化来确定配置是否生效
/** * 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);
/** * 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) {
/** * 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));
/** * 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; }
/** * 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);
/** * 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(); }
/** * 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%'; +------------------+-----------------------------------------------------------+
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.
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) {} }
/** * 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); }
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.
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
'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', # 无欲则幸福
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) {} }
/** * 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); }
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
<?php namespace App\Api\Controllers; use App\Http\Controllers\Controller; use Dingo\Api\Routing\Helpers; class BaseController extends Controller { use Helpers; }
此时我们再去创建对数据的api时就可以继承这个控制器并可以使用 Dingo Api 的 Helpers 函数.
比如在此目录下创建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'); }); });
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'); }); });
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')); }
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,
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 */ functionconfig($key = null, $default = null) { if (is_null($key)) { returnapp('config'); }
if (is_array($key)) { returnapp('config')->set($key); }
returnapp('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 */ protectedfunctionsendRequestThroughRouter($request) { $this->app->instance('request', $request);
use Maatwebsite\Excel\Concerns\FromCollection; use Maatwebsite\Excel\Concerns\WithStrictNullComparison;
class InvoicesExport implements FromCollection, WithStrictNullComparison { public function __construct(InvoicesRepository $invoices) { $this->invoices = $invoices; }
public function collection() { return $this->invoices->all(); } }
use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use Maatwebsite\Excel\Concerns\WithColumnFormatting; use Maatwebsite\Excel\Concerns\WithMapping;
class InvoicesExport implements WithColumnFormatting, WithMapping { public function map($invoice): array { return [ $invoice->invoice_number, Date::dateTimeToExcel($invoice->created_at), $invoice->total ]; }
use Maatwebsite\Excel\Concerns\WithEvents; use Maatwebsite\Excel\Events\BeforeExport; use Maatwebsite\Excel\Events\BeforeWriting; use Maatwebsite\Excel\Events\BeforeSheet;
class InvoicesExport implements WithEvents { /** * @return array */ public function registerEvents(): array { return [ // Handle by a closure. BeforeExport::class => function(BeforeExport $event) { $event->writer->getProperties()->setCreator('Patrick'); },
// Array callable, refering to a static method. BeforeWriting::class => [self::class, 'beforeWriting'],
// Using a class with an __invoke method. BeforeSheet::class => new BeforeSheetHandler() ]; }
public static function beforeWriting(BeforeWriting $event) { // } }
use Maatwebsite\Excel\Concerns\WithEvents; use Maatwebsite\Excel\Concerns\RegistersEventListeners; use Maatwebsite\Excel\Events\BeforeExport; use Maatwebsite\Excel\Events\BeforeWriting; use Maatwebsite\Excel\Events\BeforeSheet; use Maatwebsite\Excel\Events\AfterSheet;
class InvoicesExport implements WithEvents { use Exportable, RegistersEventListeners;
public static function beforeExport(BeforeExport $event) { // }
public static function beforeWriting(BeforeWriting $event) { // }
public static function beforeSheet(BeforeSheet $event) { // }
public static function afterSheet(AfterSheet $event) { // } }
可用的事件
Event name
Payload
Explanation
Maatwebsite\Excel\Events\BeforeExport
$event->writer : Writer
Event gets raised at the start of the process.
Maatwebsite\Excel\Events\BeforeWriting
$event->writer : Writer
Event gets raised before the download/store starts.
Maatwebsite\Excel\Events\BeforeSheet
$event->sheet : Sheet
Event gets raised just after the sheet is created.
Maatwebsite\Excel\Events\AfterSheet
$event->sheet : Sheet
Event gets raised at the end of the sheet process.
// When passing the callback as 2nd param, the disk will be the default disk. Excel::assertStored('filename.xlsx', function(InvoicesExport $export) { return true; }); }
// When passing the callback as 2nd param, the disk will be the default disk. Excel::assertQueued('filename.xlsx', function(InvoicesExport $export) { return true; }); }
Laravel 提供了 service container 让我们方便实现 依赖注入,而service provider则是我们注册及管理 service container 的地方。
事实上 Laravel 内部所有的核心组件都是使用 service provider 统一管理,除了可以用来管理 package 外,也可以用来管理自己写的物件。
定义
As Bootstrapper
我们知道 Laravel 提供了 service container,方便我们实现SOLID的依赖倒转原则,当 type hint 搭配 interface 时,需要自己下App::bind(),Laravel 才知道要载入什麽物件,但App::bind()要写在哪裡呢?Laravel 提供了service provider,专门负责App::bind()。
我们可以在config/app.php的providers看到所有的 package,事实上 Laravel 核心与其他 package 都是靠 service provider 载入。
As Organizer
Taylor 在书中一直强调 : 不要认为只有package才会使用service provider,它可以用来管理自己的service container,也就是说,若因为需求而需要垫interface时,可以把 service provider 当成Simple Factory pattern 使用,将变化封装在 service provider 内,将来需求若有变化,只要改 service provider 即可,其他使用该 interface 的程式皆不必修改。Laravel: From Apprentice To Artisan
use Illuminate\Support\ServiceProvider; use App\Contracts\PostRepositoryInterface; use App\Repositories\PostRepository; use App\Repositories\MyRepository;
/** * Class RepositoryServiceProvider * @package App\Providers */ class RepositoryServiceProvider extends ServiceProvider { /** * Bootstrap the application services. * * @return void */ public function boot() { // }
/** * Register the application services. * * @return void */ public function register() { $this->app->bind( PostRepositoryInterface::class, PostRepository::class ); } }
24 行
1 2 3 4 5 6 7 8 9 10 11 12
/** * Register the application services. * * @return void */ public function register() { $this->app->bind( PostRepositoryInterface::class, PostRepository::class ); }
在config/app.php的providers中 service provider,都会在 Laravel 一启动时做 register 与 binding,若一些 service container 较少被使用,你想在该 service container 实际被使用才做 register 与 binding,以加快 Laravel 启动,可以使用deferred provider。
加入$defer
app/Providers/RepositoryServiceProvider.php
1 2 3 4 5 6 7 8 9 10 11 12
class RepositoryServiceProvider extends ServiceProvider {
/** * Indicates if loading of the provider is deferred. * * @var bool */ protected $defer = true;
... }
在自己的 service provider 内加入$defer property 为 true。
加入 provides()
app/Providers/RepositoryServiceProvider.php
1 2 3 4 5 6 7 8 9 10 11 12
class RepositoryServiceProvider extends ServiceProvider { /** * Get the services provided by the provider * * @return array */ public function provides() { return [PostRepositoryInterface::class]; } }
在provides()回传该 service provider 所要处理的完整 interface 名称。
删除 service.json
1
$ php artisan clear-compiled
所有要启动的 service provider 都会被 compile 在bootstrap/cache/service.json,因为我们刚刚将PostRepositoryServiceProvider改成deferred provider,所以必须删除service.json重新建立。
Laravel 重新启动后,会重新建立service.json,在providers属性,会列出所有 service provider,因为我们刚刚将PostRepositoryServiceProvider加上$deffered = true,所以现在defferred属性会有该 service provider,而provides()所传回的 interface,正是物件的 property。
MySQL Workbench 是 MySQL 官方提供的跨平台 MySQL 客户端图形化操作软件,Brandon Eckenrode 为我们创建了一个插件,通过该插件我们可以将 MySQL Workbench 的模型导出为遵循 PSR-2 编码规范的 Laravel 迁移文件。在导出过程中,每个迁移文件会被生成并保存为名称与之相应的迁移文件。
- Use the alias: Active::getClassIf($condition, $activeClass = 'active', $inactiveClass = '')
- Use the application container: app('active')->getClassIf($condition, $activeClass = 'active', $inactiveClass = '')
- Use the helper function: active_class($condition, $activeClass = 'active', $inactiveClass = '')
Explanation: if the $condition is true, the value of $activeClass is returned, otherwise the value of $inactiveClass is returned. The package comes with several methods to help you create conditions easier. You will get the class as a string as the result of these API.
All of checking methods return boolean result (true or false). You can use the result in the condition of active_class or write your own expression.
Check the whole URI
Usage:
- Use the alias: Active::checkUri(array $uris)
- Use the application container: app('active')->checkUri(array $uris)
- Use the helper function: if_uri(array $uris)
Explanation: you give an array of URI, the package will return true if the current URI is in your array. Remember that an URI does not begin with the slash (/) except the root.
Check the URI with some patterns
Usage:
- Use the alias: Active::checkUriPattern(array $patterns)
- Use the application container: app('active')->checkUriPattern(array $patterns)
- Use the helper function: if_uri_pattern(array $patterns)
Explanation: you give an array of patterns, the package will return true if the current URI matches one of the given pattern. Asterisks may be used in the patterns to indicate wildcards.
Check the query string
Usage:
- Use the alias: Active::checkQuery($key, $value)
- Use the application container: app('active')->checkQuery($key, $value)
- Use the helper function: if_query($key, $value)
Explanation: the package will return true if one of the following condition is true:
- The current query string contains a parameter named $key with any value and the value of $value is false.
- The current query string does not contain a parameter named $key and the value of $value is null.
- The current query string contains a parameter named $key whose value is a string equals to $value.
- The current query string contains a parameter named $key whose value is an array that contain the $value.
- Use the alias: Active::checkRoute(array $routes)
- Use the application container: app('active')->checkRoute(array $routes)
- Use the helper function: if_route(array $routes)
Explanation: you give an array of route names, the package will return true if the name of the current route (which can be null) is in your array.
Check the route name with some patterns
Usage:
- Use the alias: Active::checkRoutePattern(array $patterns)
- Use the application container: app('active')->checkRoutePattern(array $patterns)
- Use the helper function: if_route_pattern(array $patterns)
Explanation: you give an array of patterns, the package will return true if the name of the current route (which can be null) matches one of the given pattern. Asterisks may be used in the patterns to indicate wildcards.
Check the route parameter value
Usage:
- Use the alias: Active::checkRouteParam($key, $value)
- Use the application container: app('active')->checkRouteParam($key, $value)
- Use the helper function: if_route_param($key, $value)
Explanation: the package will return true if one of the following condition is true:
- The current route contains a parameter named $key whose value is $value.
- The current route does not contain a parameter named $key and the value of $value is null.
Read more about route parameter in the Laravel documentation.
获取当前值
Get the current action
Usage:
- Use the alias: Active::getAction()
- Use the application container: app('active')->getAction()
- Use the helper function: current_action()
Explanation: if the current route is bound to a class method, the result will be a string like App\Http\Controllers\YourController@yourMethod. If the route is bound to a closure, the result will be the Closure string.
Get the current controller class
Usage:
- Use the alias: Active::getController()
- Use the application container: app('active')->getController()
- Use the helper function: current_controller()
Explanation: if the current route is bound to a class method, the result will be the full qualified class name of the controller class, like App\Http\Controllers\YourController. If the route is bound to a closure, the result will be the Closure string.
Get the current controller method
Usage:
- Use the alias: Active::getMethod()
- Use the application container: app('active')->getMethod()
- Use the helper function: current_method()
Explanation: if the current route is bound to a class method, the result will be the name of the controller method. like yourMethod. If the route is bound to a closure, the result will be the empty string.
Example
The example below illustrate the usage of this package in a sidebar with Bootstrap link group:
I believe this is designed this way to limit number of SQL queries. If you need to get actual data saved in the database, you need to obtain this record explicitely, just the way you did :)
INSERT query doesn’t return actual row.
3. 事件中绝对不要返回 fasle
因为在 PHP 5.5 中 这样说明
Stopping The Propagation Of An Event Sometimes, you may wish to stop the propagation of an event to other listeners. You may do so by returning false from your listener’s handle method.
use Order\Action\Hunter; use Order\Models\Filters\OrderHunterFilter; use Order\Models\OrderHunter; use Order\Models\Resources\EarnResource; use Site\Tests\Base\SiteTestCase; use System\Models\PamAccount;
12. 类内部调用使用 self (self_accessor)
1 2 3 4 5 6 7 8
/** * @param int $account_id 用户ID * @return StatisticsRangeFilter */ public function account($account_id): self { return $this->where('account_id', $account_id); }