[Laravel8.x] maatwebsite/excelのExcel出力時にスタイリング

maatwebsite/excelを使ってExcelを出力する際に、stylingをしたい。

export.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
  .hoge {
    color: green;
  }
</style>
</head>
<body>
  <table>
  <thead>
  <tr>
    <th class="hoge">id</th>
    <th>name</th>
    <th>email</th>
  </tr>
  </thead>
  <tbody>
    @foreach ($users as $user)
    <tr>
      <td class="hoge">{{$user->id}}</td>
      <td style="color:red;">{{$user->name}}</td>
      <td>{{$user->email}}</td>
    </tr>
    @endforeach
  </tbody>
</table>
</body>
</html>


これだと、classで定義したcolorがexcel上に反映されない。
致し方がない、inlineで書くか。

### inline

<th style="background-color:#FF0000;color:#fff">id</th>

なるほど。

[Laravel8.x] csvではなくExcel(xlsx)を出力する

### ライブラリインストール
$ composer require maatwebsite/excel

app/config.php

'providers' => [
    Maatwebsite\Excel\ExcelServiceProvider::class,
],
'aliases' => [
    'Excel' => Maatwebsite\Excel\Facades\Excel::class,
],

$ php artisan vendor:publish –provider=”Maatwebsite\Excel\ExcelServiceProvider”

$ php artisan make:export Export
app/Exports/Export.php

namespace App\Exports;

// use Maatwebsite\Excel\Concerns\FromCollection;
use Illuminate\Contracts\View\View;
use Maatwebsite\Excel\Concerns\FromView;

class Export implements FromView
{
    /**
    * @return \Illuminate\Support\Collection
    */
    // public function collection()
    // {
    //     //
    // }
    private $view;

    public function __contruct(View $view){
    	$this->view = $view;
    }

    public function view(): View {
    	return $this->view;
    }
}

routing

Route::get('/export', [App\Http\Controllers\AdminController::class, 'export']);

controller

use App\Http\Requests\UserFormRequest;
use App\Exports\Export;

    public function export(){
        $users = User::get();
        $view = view('admin.export', compact('users'));
        return \Excel::download(new Export($view), 'users.xlsx');
    }

export.blade.php

<table>
  <thead>
  <tr>
    <th>id</th>
    <th>name</th>
    <th>email</th>
  </tr>
  </thead>
  <tbody>
    @foreach ($users as $user)
    <tr>
      <td>{{$user->id}}</td>
      <td>{{$user->name}}</td>
      <td>{{$user->email}}</td>
    </tr>
    @endforeach
  </tbody>
</table>

ほうほう、中々デキる、こやつ

[Laravel8.16.0] UserAgentを取得してPC/SPのviewを分けて表示する

UserAgentの取得には、jenssegers/agentを使います。

github: https://github.com/jenssegers/agent

$ composer require jenssegers/agent

config/app.php

    'providers' => [
        // 省略
        Jenssegers\Agent\AgentServiceProvider::class,
    ],

'aliases' => [
        //省略
        'Agent' => Jenssegers\Agent\Facades\Agent::class,
    ],

AdminController.php

use Jenssegers\Agent\Agent;

    public function useragent(){
        if(\Agent::isMobile()){
            return view('admin.sp');
        }
        return view('admin.pc');
    }


なるほど、相当簡単に実装できる。
これをunitテストでやりたい。

$ php artisan make:test UserRegisterTest

public function testExample()
    {
        $response = $this->get('/');

        $response->dumpHeaders();
    }

$ vendor/bin/phpunit tests/Feature/UserAgentTest.php

public function testExample()
    {
        $response = $this->get('/admin/useragent');
        // $response->assertStatus(200);
        $this->assertSame(2, $response);
    }

$ vendor/bin/phpunit tests/Feature/UserAgentTest.php
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

なんでだ? $this->getだとheader情報しか返ってこないのか。
UnitTestはもうちょっとやりこまないとダメやな。

[Laravel8.16.0] bumbummen99/shoppingcartのdatabaseの使い方

既にdbに格納されている時にstoreするとエラーになるので、都度eraseしてupdateする。

### 商品追加時
\Cart::addする時に、一回db上のデータをeraseで消してから、最新のカート情報を保存する

\Cart::add([
            [
                'id'=>$inputs['productId'],
                'name'=>$inputs['name'],
                'qty'=>$inputs['qty'],
                'price'=>$inputs['price'],
                'weight'=>0,
                'options' => [
                    // 省略
                ],
            ]
        ]);
        // DB update
        $user = Auth::user();
        \Cart::erase($user['name']);
        \Cart::store($user['name']);

### 数量変更時

public function change(Request $request){
        
        // 数量変更
    	$rowId = $request->input('rowId');
    	$qty = $request->input('qty');
    	\Cart::update($rowId, $qty); 

     // DB update
        $user = Auth::user();
        \Cart::erase($user['name']);
        \Cart::store($user['name']);
        return redirect('/shoppingcart');
    }

### 個別商品削除時
L destroyではないので、都度updateする

        \Cart::remove($request->row_id);
        $user = Auth::user();
        \Cart::erase($user['name']);
        \Cart::store($user['name']);

        return \Cart::content();

### ログイン時
L ログイン時は当然restoreする

	$user = Auth::user();
	\Cart::restore($user['name']);

なるほどね、logout時・session timeout時にstoreして、login時にrestoreしようかと思ったけど、controller見つからないし、どうしようかと思ったわ。
一気に解決した。

[Laravel8.16.0] bumbummen99/shoppingcartのsessionをdatabaseに保存したい

github: https://github.com/bumbummen99/LaravelShoppingcart

To make your life easy, the package also includes a ready to use migration which you can publish by running:

php artisan vendor:publish --provider="Gloudemans\Shoppingcart\ShoppingcartServiceProvider" --tag="migrations"

This will place a shoppingcart table's migration file into database/migrations directory. Now all you have to do is run php artisan migrate to migrate your database.

$ php artisan vendor:publish –provider=”Gloudemans\Shoppingcart\ShoppingcartServiceProvider” –tag=”migrations”

    public function up()
    {
        Schema::create(config('cart.database.table'), function (Blueprint $table) {
            $table->string('identifier');
            $table->string('instance');
            $table->longText('content');
            $table->nullableTimestamps();

            $table->primary(['identifier', 'instance']);
        });
    }

$ php artisan migrate
Migrating: 2018_12_23_120000_create_shoppingcart_table
Migrated: 2018_12_23_120000_create_shoppingcart_table (53.84ms)

mysql> describe shoppingcart;
+————+————–+——+—–+———+——-+
| Field | Type | Null | Key | Default | Extra |
+————+————–+——+—–+———+——-+
| identifier | varchar(255) | NO | PRI | NULL | |
| instance | varchar(255) | NO | PRI | NULL | |
| content | longtext | NO | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+————+————–+——+—–+———+——-+
5 rows in set (0.01 sec)

保存

$user = Auth::user();
\Cart::store($user['name']);


mysql> select * from shoppingcart;


呼び出し

	$user = Auth::user();
	\Cart::restore($user['name']);
        \Cart::erase($user['name']);

保存呼び出しはできる!

ログイン時とログアウト時に保存と呼び出しを書き加えたい。
問題は、controllerのどこに書くかだな。。。。

[Laravel8.16.0] Google Analytics APIを使う

まず、analytics-*-*.p12ファイルですが、publicには置けないので、resourcesフォルダの下にstaticフォルダを作成し、そこに置きます。
※storageやpublicに置かない様にする。

続いて、google-api-php-clientをインストールします。
githubで最新を確認する。この記事を書いている時点では^2.7が最新です。
https://github.com/googleapis/google-api-php-client

$ composer require google/apiclient:”^2.7″

AdminController.php (*test)
L resource_path(‘static/analytics-*-*.p12’)で、p12を指定する

    public function index(){

        $client = new Google_Client();
        $client->setApplicationName("Client_Library_Examples");
        $client->setDeveloperKey(resource_path('static/analytics-*-*.p12'));
        dd($client->getAccessToken());

    	$user = Auth::user();
        return view('admin.index',compact('user'));
    }

nullが返ってくるとOKっぽい。

テスト環境と同じ様に書いてみます。

use Google_Client;
use Google_Service_Analytics;

$service_account_email = '*.iam.gserviceaccount.com';
      $key = file_get_contents(resource_path('static/analytics-*-*.p12'));
      $profile = '*';

      $client = new Google_Client();
      $analytics = new Google_Service_Analytics($client);

      $cred = new Google_Auth_AssertionCredentials(
        $service_account_email,
        array(Google_Service_Analytics::ANALYTICS_READONLY),
        $key
      );

      $client->setAssertionCredentials($cred);
      if($client->getAuth()->isAccessTokenExpired()){
        $client->getAuth()->refreshTokenWithAssertion($cred);
      }

Class ‘App\Http\Controllers\Google_Auth_AssertionCredentials’ not foundのエラー

issueに原因が載ってます。
https://github.com/GoogleCloudPlatform/nodejs-docs-samples/issues/158

Oh, I've just found https://github.com/google/google-api-php-client/blob/master/UPGRADING.md

So it's the docs at https://developers.google.com/analytics/devguides/reporting/core/v3/quickstart/service-php that are wrong.

GoogleDevコンソールで、「サービス アカウント キーの作成」からjsonファイルを作成して、以下に書き換えます。

use Google_Client;
use Google_Service_Analytics;

$profile = '*';

        $client = new Google_Client();
        $client->setScopes(array('https://www.googleapis.com/auth/analytics.readonly'));

        $client->setSubject('*.iam.gserviceaccount.com');
        $client->setAuthConfig(array(
            'type' => 'service_account',
            'client_email' => '*.iam.gserviceaccount.com',
            'client_id' => '*',
            'private_key' => "-----BEGIN PRIVATE KEY-----***-----END PRIVATE KEY-----\n"
        ));

        $analytics= new Google_Service_Analytics($client);

         $result = $analytics->data_ga->get(
         'ga:' . $profile,
         'yesterday',
         '0daysAgo',
         'ga:pageviews',
         array(
           // "dimensions" => 'ga:pageTitle',
           "dimensions" => 'ga:pagePath',
           "sort" => '-ga:pageviews',
           "max-results" => '10',
         )
        );

        $data = $result->rows;
        dd($data);

これでいけました。結局p12は使わない。
う〜ん、AnalyticsAPIは使うの緊張する。

[Laravel8.15.0]LaravelShoppingCartのoptionの使い方

\Cart::addする際は、’options’=>[‘key’=>’value’]で追加する。

$product = \App\Models\Product::find($request->productId);
        \Cart::add([
            [
                'id'=>$product->id,
                'name'=>$product->name,
                'qty'=>$request->qty,
                'price'=>'500',
                'weight'=>'1',
                'options' => [
                    'size' => "a商事",
                ],
            ]
        ]);
        return \Cart::content();

そうすると、optionsの中に連想配列として入る。

vue.jsの中で呼び出すには、jsonの連想配列に従って呼び出せば良い。

axios.post(url, params)
    						.then(function(response){
    							self.cartItems = response.data;
    						});
                        var obj = JSON.parse(JSON.stringify(self.cartItems))
                        var ids = Object.keys(obj)
                        console.log(obj[ids]['options']['size']);

なるほど、optionは配列なので、複数入れられるってことね。
連想配列と二次元配列の違いがよくわかってなかったが、完全に理解した。

[Laravel8.12.3] shoppingcartの数量変更処理を実装する

ここに、”rowId”:”a775bac9cff7dec2b984e023b95206aa”,”name”:”3\u756a\u76ee\u306e\u5546\u54c1\u540d”,”qty”:”1″の商品がある。

qtyを5に変更する

public function change(){
    	$rowID = 'a775bac9cff7dec2b984e023b95206aa';
    	\Cart::update($rowId, 5); 
    	return \Cart::content();
    }

“qty”:”5″ にupdateされた。

これをformのPOSTで実装する。
– rowIdとqty(数量)をpostで送信する。
resources/views/carts/index.blade.php

<form action="/change" method="POST">
					{{ csrf_field() }}
						<input type="hidden" name="rowId" v-model="rowId">
						<input v-model="cartItem.qty" name="qty" style="width:50px;margin-right:10px"><input type="submit" class="btn btn-secondary btn-sm" value="変更"></td>
					</form>

route/web.php

Route::match(['get', 'post'],'change', [App\Http\Controllers\CartController::class, 'change']);

app/Http/Cotnrollers/CartController.php
– updateしたら、cartにリダイレクトさせる。

    public function change(Request $request){
    	$rowId = $request->input('rowId');
    	$qty = $request->input('qty');
    	\Cart::update($rowId, $qty); 
    	return redirect('/carts');
    }

予約確認画面では、そのままjsonで取得して、予約が完了したらCart::destroy()でcartのセッションを全て削除すれば良い。
OK、取り敢えず買い物カゴの仕組み・実装はマスターしたので設計書を書いていくか。。

[Laravel8.12.3] shoppingcart (bumbummen99) を実装する2

bumbummen99の\Cart::addの書き方が分かったので、気を取り直して、続きを書いていきます。

app/Http/Cotnrollers/Ajax/CartController.php

public function store(Request $request)
    {
        //
        $product = \App\Models\Product::find($request->productId);
        \Cart::add([
            [
                'id'=>$product->id,
                'name'=>$product->name,
                'qty'=>$request->qty,
                'price'=>'500',
                'weight'=>'1'
            ]
        ]);
        return \Cart::content();
    }

### カートの一覧作成
app/Http/Cotnrollers/CartController.php

class CartController extends Controller
{
    //Cart
    public function index(){
    	return view('carts.index');
    }
}

resources/views/carts/index.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
	<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet">
</head>
</head>
<body style="padding-top: 10px;">
	<div id="app">
		<div class="container">
			<h1>カートの中身</h1>
			<table class="table">
				<tr>
					<th>商品</th>
					<th>個数</th>
					<th>価格</th>
					<th>小計</th>
					<th></th>
				</tr>
				<tr v-for="(cartItem,rowId) in carts.items">
					<td><span v-text="cartItem.name"></span>(サイズ: <!-- <span v-text="cartItem.options.size"></span> -->)</td>
					<td v-text="cartItem.qty"></td>
					<td v-text="cartItem.price"></td>
					<td v-text="cartItem.subtotal"></td>
					<td class="text-right">
						<button type="button" class="btn btn-danger btn-sm" @click="removeItem(rowId)">削除</button>
					</td>
				</tr>
				<tr>
					<td colspan="3"></td>
					<th>小計</th>
					<th v-text="carts.subtotal"></th>
				</tr>
				<tr>
					<td colspan="3"></td>
					<th>税</th>
					<th v-text="carts.tax"></th>
				</tr>
				<tr>
					<td colspan="3"></td>
					<th>合計</th>
					<th v-text="carts.total"></th>
				</tr>
			</table>
			<div class="text-right">
				<button type="button" class="btn btn-success">お会計へ</button>
			</div>
		</div>
	</div>
	<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
	<script>
		new Vue({
			el: '#app',
			data: {
				carts: {}
			},
			methods: {
				removeItem: function(rowId){
					if(confirm('商品を削除します。よろしいですか?')){

						var self = this;
						var cart = this.carts.item[rowId];
						var url = '/ajax/carts/' + cart.id;
						var params = {
							row_id: rowId,
							_method: 'delete'
						};
						axios.post(url, params)
							.then(function(response){
								self.getCarts();
							});
					}
				},
				getCarts: function(){

					var self = this;
					axios.get('/ajax/carts')
						.then(function(response){
							self.carts = response.data;
						});
				}
			},
			mounted: function(){
				this.getCarts();
			}
		});
	</script>
</body>
</html>

app/Http/Cotnrollers/Ajax/CartController.php

public function index()
    {
        //
        return [
            'items'=>\Cart::content(),
            'subtotal'=>\Cart::subtotal(),
            'tax'=>\Cart::tax(),
            'total'=>\Cart::total(),
        ];
    }

public function destroy(Request $request)
    {
        //
        return \Cart::remove($request->row_id);
    }

http://192.168.33.10:8000/ajax/carts

http://192.168.33.10:8000/carts

うん、いいけど、カートページで数量を変更するボタンが絶対必要だな。

[Laravel8.12.3] bumbummen99 / LaravelShoppingcartの使い方

READ.meをよく読む
bumbummen99/LaravelShoppingcart

Cart::add()
Adding an item to the cart is really simple, you just use the add() method, which accepts a variety of parameters.

In its most basic form you can specify the id, name, quantity, price and weight of the product you'd like to add to the cart.

(1)id, (2)name, (3)quantity, (4)price, (5)weight の5項目は入力必須とのこと。

public function index(){
    	$product = \App\Models\Product::find(1);
        \Cart::add([
            [
            	'id'=>$product->id,
            	'name' => $product->name,
            	'qty' => '1',
                'price' => '100',
                'weight' => '1'

            ]
        ]);
        return \Cart::content();
    }

例えば、ここでnameが抜けてた場合

public function index(){
    	$product = \App\Models\Product::find(1);
        \Cart::add([
            [
            	'id'=>$product->id,
            	// 'name' => $product->name,
            	'qty' => '1',
                'price' => '100',
                'weight' => '1'

            ]
        ]);
        return \Cart::content();
    	// return view('products.index');
    }

undefinedでエラーになる。

公式に書いてある様に、optionで(1)id, (2)name, (3)quantity, (4)price, (5)weight 以外に値を持たせることも出来るみたいね。
流石にlaravelのversion upのスピードにユーザやGoogleに引っかかる記事がキャッチアップしきれてない感があるな。