[Laravel8.x:shoppingcart] メーカーごとの合計値を算出

bumbummen99/shoppingcart で、$totalでカートの合計値は算出できるが、メーカーごとの合計値を算出したい時

$subtotal_dataという配列に初期値’initial’=>0を入れ、カートのメーカーがinitalでなければ$subtotal_dataにメーカーとメーカーの注文金額を入れていき、最後にarray_shiftで、’initial’=>0を削除する。

    	$carts = \Cart::content();
        $subtotal_data=['initial'=>0];
        foreach($carts as $cart){
            foreach($subtotal_data as $key => $value){
                if($key === $cart->options->supplier_code) {
                    $subtotal_data[$cart->options->supplier_code] = $value + $cart->price * $cart->qty;
                } elseif(!array_key_exists($cart->options->supplier_code, $subtotal_data)) {
                    $subtotal_data[$cart->options->supplier_code] = $cart->price * $cart->qty;
                }
            }
        }
        array_shift($subtotal_data);
        dd($subtotal_data);

array:2 [▼
“a” => 9910.0
“b” => 2025.0
]

あー、これ、送料計算できそうやな^^
こうすれば、メーカーごとの最も高い金額を持たせることができる。

$carts = \Cart::content();
        $subtotal_data=['initial'=>0];
        foreach($carts as $cart){
            foreach($subtotal_data as $key => $value){
                if($key === $cart->options->supplier_code) {
                    if($subtotal_data[$cart->options->supplier_code] < $cart->price){
                        $subtotal_data[$cart->options->supplier_code] = $cart->price;
                    }
                } elseif(!array_key_exists($cart->options->supplier_code, $subtotal_data)) {
                    $subtotal_data[$cart->options->supplier_code] = $cart->price;
                }
            }
        }
        array_shift($subtotal_data);
        dd($subtotal_data);

array:2 [▼
“a” => 4800.0
“b” => 2025.0
]

なるほどー、全て解決したっぽい。

[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

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