上一篇 文章對 spatie/laravel-tail
這一擴充套件包進行了分析,受到不少網友的好評,今天繼續講解如何實現 PHP-Vars-To-Js-Transformer
這一擴充套件包。
介紹
laracasts 出品的 PHP-Vars-To-Js-Transformer 包可自動將 PHP 變數轉化為 JavaScript 變數。
使用說明
JavaScript::put('foo', 'bar');
// window.foo = "bar";
JavaScript::put([
'foo' => 'bar',
'age' => 29
]);
// window.foo = "bar";window.age = 29;"
實現
PHP-Vars-To-Js-Transformer 擴充套件包主要完成兩項工作
- 將 PHP 變數轉化為 JavaScript 變數;
- 利用檢視事件自動輸出 JavaScript 變數;
定義類,該類傳入需要繫結的檢視以及 JavaScript
變數的作用域
namespace App\Services\JavaScript;
class Transformer
{
/**
* 名稱空間
*
* @var string
*/
private $namespace;
/**
* 要輸出變數的檢視
*
* @var array
*/
private $views;
function __construct($views, string $namespace = 'window')
{
$this->namespace = $namespace;
$this->views = str_replace('/', '.', (array)$views);
}
// 主要業務邏輯,todo
public function put()
{
}
轉化變數
put
函式支援兩種型別的傳參
JavaScript::put('foo', 'bar')
JavaScript::put($arr)
這兩種傳參都需要標準化成陣列
use InvalidArgumentException;
public function put()
{
$input = $this->normalizeInput(func_get_args());
}
/**
* 格式化輸入
*
* @param string | array $variables
* @throws InvalidArgumentException
* @return array
*/
public function normalizeInput($arguments) : array
{
if( is_array($arguments[0]) ){
return $arguments[0];
}
if(count($arguments) == 2){
return [
$arguments[0] => $arguments[1]
];
}
throw new InvalidArgumentException('引數輸入錯誤');
}
將輸入標準化成陣列後,還需要進一步將其轉化為 JavaScript 變數,關鍵點在於 PHP 變數的值可能有多種型別(基本型別、陣列、物件等等),需要進行不同的處理
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use JsonSerializable;
public function put()
{
$input = $this->normalizeInput(func_get_args());
$js = $this->constructJavaScript($input);
}
/**
* 轉化 PHP 變數
*
* @param array $variables PHP 變數
* @return string
*/
public function constructJavaScript($variables)
{
return collect($variables)->map(function($data, $key){
return "{$this->namespace}.{$key} = ".$this->convertToJavaScript($data).";";
})->implode('');
}
/**
* 格式化值
*
* @param mix $data
*
* @return mix
*/
public function convertToJavaScript($data)
{
if($data instanceof Jsonable){
return $data->toJson();
}
if ($data instanceof JsonSerializable) {
return json_encode($data->jsonSerialize());
}
if ($data instanceof Arrayable) {
return json_encode($data->toArray());
}
return json_encode($data);
}
當名稱空間不為 window
時,還需要新增額外的宣告語句。例如,傳入的作用域為 laravel
,則需要新增宣告語句 window.laravel = window.laravel || {};
,具體實現如下
public function put()
{
$input = $this->normalizeInput(func_get_args());
$js = $this->constructJavaScript($input);
$js = $this->constructNamespace().$js;
}
/**
* 構造名稱空間
*
* @return string
*/
public function constructNamespace() : string
{
if($this->namespace == 'window'){
return '';
}
return "window.{$this->namespace} = window.{$this->namespace} || {};";
}
輸出變數
為繫結的檢視新增對應的事件,以便自動輸出變數
public function put()
{
$input = $this->normalizeInput(func_get_args());
$js = $this->constructJavaScript($input);
$js = $this->constructNamespace().$js;
foreach ($this->views as $view) {
app('events')->listen("composing: {$view}", function () use ($js) {
echo "<script>{$js}</script>";
});
}
}
完整程式碼
<?php
namespace App\Services\JavaScript;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use InvalidArgumentException;
use JsonSerializable;
class Transformer
{
/**
* 名稱空間
*
* @var string
*/
private $namespace;
/**
* 要輸出變數的檢視
*
* @var array
*/
private $views;
function __construct($views, string $namespace = 'window')
{
$this->namespace = $namespace;
$this->views = str_replace('/', '.', (array)$views);
}
public function put()
{
$input = $this->normalizeInput(func_get_args());
$js = $this->constructJavaScript($input);
$js = $this->constructNamespace().$js;
foreach ($this->views as $view) {
app('events')->listen("composing: {$view}", function () use ($js) {
echo "<script>{$js}</script>";
});
}
}
/**
* 轉化 PHP 變數
*
* @param array $variables PHP 變數
* @return string
*/
public function constructJavaScript(array $variables) : string
{
return collect($variables)->map(function($data, $key){
return "{$this->namespace}.{$key} = ".$this->convertToJavaScript($data).";";
})->implode('');
}
/**
* 格式化值
*
* @param mix $data
*
* @return mix
*/
public function convertToJavaScript($data)
{
if($data instanceof Jsonable){
return $data->toJson();
}
if ($data instanceof JsonSerializable) {
return json_encode($data->jsonSerialize());
}
if ($data instanceof Arrayable) {
return json_encode($data->toArray());
}
return json_encode($data);
}
/**
* 格式化輸入
*
* @param string | array $variables
* @throws InvalidArgumentException
* @return array
*/
public function normalizeInput($arguments) : array
{
if( is_array($arguments[0]) ){
return $arguments[0];
}
if(count($arguments) == 2){
return [
$arguments[0] => $arguments[1]
];
}
throw new InvalidArgumentException('引數輸入錯誤');
}
/**
* 構造名稱空間
*
* @return string
*/
public function constructNamespace() : string
{
if($this->namespace == 'window'){
return '';
}
return "window.{$this->namespace} = window.{$this->namespace} || {};";
}
}
執行
$input = [
'foo' => 'bar',
'age' => 29,
'collection' => collect(['aaa', 'bbb', 'ccc'])
];
$views = ['footer'];
$transform = new Transformer($views, 'laravel');
$transform->put($input);
優化
Transformer
類主要完成了兩項工作:轉化變數以及繫結變數到檢視。該類的實現違反了 開放-封閉 原則
實體(類、方法等)應當對擴充套件開放,對修改封閉。
對於繫結變數到檢視這一行為而言,不同的框架有不同的實現方法,是可變的行為,應當將其分離出來,隱藏於介面背後。
<?php
namespace App\Services\JavaScript;
interface ViewBinderInterface
{
/**
* 繫結變數到檢視
*
* @param string $js
*/
public function bind($js);
}
Laravel 對繫結變數到檢視這一行為的實現
<?php
namespace App\Services\JavaScript;
use Illuminate\Contracts\Events\Dispatcher;
class ViewBinder implements ViewBinderInterface
{
/**
* 事件分發器
*
* @var Dispatcher
*/
protected $event;
/**
* 變數繫結的檢視
*
* @var string
*/
protected $views;
/**
* Create a new Laravel view binder instance.
*
* @param Dispatcher $event
* @param string|array $views
*/
public function __construct(Dispatcher $event, $views)
{
$this->event = $event;
$this->views = str_replace('/', '.', (array)$views);
}
/**
* Bind the given JavaScript to the view.
*
* @param string $js
*/
public function bind($js)
{
foreach ($this->views as $view) {
$this->event->listen("composing: {$view}", function () use ($js) {
echo "<script>{$js}</script>";
});
}
}
}
Transformer
類就可以簡化成
<?php
namespace App\Services\JavaScript;
use App\Services\JavaScript\ViewBinderInterface;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use InvalidArgumentException;
use JsonSerializable;
class Transformer
{
/**
* 名稱空間
*
* @var string
*/
private $namespace;
/**
* @var App\Services\JavaScript\ViewBinderInterface
*/
private $viewBinder;
function __construct(ViewBinderInterface $viewBinder, string $namespace = 'window')
{
$this->namespace = $namespace;
$this->viewBinder = $viewBinder;
}
public function put()
{
$js = $this->constructNamespace().$this->constructJavaScript($this->normalizeInput(func_get_args()));
$this->viewBinder->bind($js);
return $js;
}
}
執行
$input = [
'foo' => 'bar',
'age' => 29,
'hello' => collect(['aaa', 'bbb', 'ccc'])
];
$viewBinder = new ViewBinder(
app('events'),
['footer']
);
$transform = new Transformer($viewBinder, 'laravel');
$transform->put($input);
整合到 Laravel 專案中
註冊容器
public function register()
{
$this->app->singleton('JavaScript', function ($app) {
return new Transformer(
new ViewBinder($app['events'], ['footer']),
'window'
);
});
}
建立門面
<?php
namespace App\Services\JavaScript;
use Illuminate\Support\Facades\Facade;
class JavaScriptFacade extends Facade
{
protected static function getFacadeAccessor()
{
return 'JavaScript';
}
}
註冊門面
'JavaScript' => App\Services\JavaScript\JavaScriptFacade::class,