もがき系プログラマの日常

もがき系エンジニアの勉強したこと、日常のこと、気になっている技術、備忘録などを紹介するブログです。

symfonyのパスワードのアルゴリズムを指定する

はじめに

こんばんは。

今回はsymfonyの備忘録です。

symfonyのユーザーなどのパスワードのアルゴリズムをしたいということがありましたので、残しておきます。

本題

めちゃめちゃかんたんで security.yaml に記載するだけです。

security:
    password_hashers:
        App\Entity\User:
            algorithm: xxxx   ← ここ

symfony.com

アルゴリズムEncoderFactory を確認すると、 auto 以外にも特定のアルゴリズムを設定できます。

例えば、 plaintextbcrypt です。

基本的に auto でも問題ないと思いますが、もし、ローカルと本番環境等でもしインストールされている拡張機能に差異があると auto だとうまく行かない場合があります

例えば 管理用のスーパユーザーをfixtureで登録する場合とかになります。

auto に設定している状態で sodium が入っている場合 、 EncoderFactoryにて SodiumPasswordEncoder.phpが選択されます。

そしてsodiumを使用してハッシュ化したパスワードが生成されます。

そしてその fixtureを sodiumが入っていない 環境で loadします。

そうするとそのユーザーでログインする場合、 sodiumが入っていないので NativePasswordEncoder.phpが選択されて、結果パスワードが合わなくなります。

終わりに

そもそも、環境差異があるって時点でアウトですが、今回はGAE + CloudSQLの環境で起きました。。。

CloudSQLにfixtureを読み込ませるさい、proxyを使ってローカルから入れる事ができたのですが、そのさいこの環境差分が起きました。

GAEのphp環境は インストール済みの拡張機能にも、動的追加可能な拡張機能にも sodiumがなかったので、ハマってしまいました。

かんたんですが、以上です。

GAEにphpのプロジェクトをデプロイ

はじめに

こんばんは。

今回は前回と同じくGCP系の備忘録です。

本題

まずは app.yaml ファイルを作成します。

runtime: php74

基本的にこれだけでいいっぽいですが、拡張を入れる場合、同ディレクトリに php.ini を作成します。

extension=grpc.so

[Date]
date.timezone= 'Asia/Tokyo'

次に、 public/index.php を用意します。

<?php

phpinfo();

これで、 deploy コマンドを実行してみます。

$ gcloud app deploy --project=XXXXX
Initializing App Engine resources...done.                                                                                                                                                                                                                                                                                                         
Services to deploy:

descriptor:                  [/path/to/example1/app.yaml]
source:                      [/path/to/example1]
target project:              [XXXXX]
target service:              [default]
target version:              [ZZZZZ]
target url:                  [https://xxxxxzzzzz.an.r.appspot.com]
target service account:      [App Engine default service account]


Do you want to continue (Y/n)?  Y

Beginning deployment of service [default]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 3 files to Google Cloud Storage                ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...done.                                                                                                                                                                                                                                                                                                                
Starting version [XXXXX/default/ZZZZZ] before promoting it.
Waiting for operation [apps/XXXXX/operations/aaaa-bbbb-cccc-dddd-eeee] to complete...done.                                                                                                                                                                                                                             
Setting traffic split for service [default]...done.                                                                                                                                                                                                                                                                                               
Deployed service [default] to [https://xxxxxzzzzz.an.r.appspot.com]

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default

To view your application in the web browser run:
  $ gcloud app browse

終わりに

かんたんですが以上です。

GCPのProjectを切り替える

はじめに

こんばんは。

最近ローカルでgcpを触っているのですが、お世話になっている会社さんのプロジェクトと、自分のテスト用のプロジェクトとで複数あつかう事になったりします。

そのためにgcpの設定を切り替える方法を備忘録で残しておきます。

本題

1. 確認

現在登録済みの設定を確認します。

$ gcloud config configurations list                                                                  
NAME        IS_ACTIVE  ACCOUNT                     PROJECT               COMPUTE_DEFAULT_ZONE  COMPUTE_DEFAULT_REGION
private    True       private_example@gmail.com
company-a  False      company_a@gmail.com
company-b  False      company_b@gmail.com

$ gcloud config list
[core]
XXXX
XXXX

Your active configuration is: [private]

2. 切り替え

company-a の設定に切り替えます。

$ gcloud config configurations activate private
Activated [company-a]. 


$ gcloud config configurations list                                                                  
NAME        IS_ACTIVE  ACCOUNT                     PROJECT               COMPUTE_DEFAULT_ZONE  COMPUTE_DEFAULT_REGION
private    False      private-example@gmail.com
company-a  True       company_a@gmail.com
company-b  False      company_b@gmail.com


$ gcloud config list
[core]
XXXX
XXXX

Your active configuration is: [company-a]

3. 追加

あたらしく company-c の設定を追加してみます。

$ gcloud config configurations create company-c
Created [company-c].
Activated [company-c].


$ gcloud config configurations list   
NAME        IS_ACTIVE  ACCOUNT                     PROJECT               COMPUTE_DEFAULT_ZONE  COMPUTE_DEFAULT_REGION
company-c  True
private    False      private-example@gmail.com
company-a  False      company_a@gmail.com
company-b  False      company_b@gmail.com


$ gcloud config list                            
[core]
XXXX
XXXX

Your active configuration is: [company-c]

4. 削除

さきほど追加した company-c の設定を削除します。

$ gcloud config configurations delete company-c 
The following configurations will be deleted:
 - company-c
Do you want to continue (Y/n)?  Y

Deleted [company-c].


$ gcloud config configurations list   
NAME        IS_ACTIVE  ACCOUNT                     PROJECT               COMPUTE_DEFAULT_ZONE  COMPUTE_DEFAULT_REGION
private    True       private-example@gmail.com
company-a  False      company_a@gmail.com
company-b  False      company_b@gmail.com

終わりに

この備忘録は未来の自分が必ず役に立つ...はず。

ElasticSearchでドキュメントの配列に、追加・削除を行いたい

はじめに

こんばんは。

今回はElasticSearchです。

保存されたドキュメントの配列に、追加・削除を行いたいという場面がありまして、なかなかうまく行かず、試行錯誤した備忘録です。

参考サイト

本題

サンプルデータ

{
  "shop_id": 10000,
  "items": [
    {
      "item_id": 100,
      "name": アイテム100,
      "price": 500,
    },
    {
      "item_id": 200,
      "name": アイテム200,
      "price": 1000,
    },
    {
      "item_id": 300,
      "name": アイテム300,
      "price": 1500,
    },
    {
      "item_id": 400,
      "name": アイテム400,
      "price": 2000,
    },
    {
      "item_id": 500,
      "name": アイテム500,
      "price": 2500,
    },
    {
      "item_id": 600,
      "name": アイテム600,
      "price": 3000,
    },
  ]
},
{
  "shop_id": 20000,
  "items": [
    {
      "item_id": 100,
      "name": アイテム100,
      "price": 500,
    },
    {
      "item_id": 200,
      "name": アイテム200,
      "price": 1000,
    },
    {
      "item_id": 700,
      "name": アイテム700,
      "price": 3500,
    },
  ]
},

削除

shop_id: 10000 のitemsのなかの、item_id: 300item_id: 600 のものだけを消したいということあると思います。

その場合、以下のような形で、削除できました!

POST _update_by_query

"query": {
    "bool": {
        "must": [
            {
                "term": {
                    "shop_id": 10000
                }
            }
        ]
    }
},
"script": {
    "lang": "painless",
    "inline": "for (item_id in params.item_ids) { ctx._source.items.removeIf(item -> item.item_id.toString().contains(item_id)) }",
    "params"': {
        "item_ids" => [
            "300",
            "600"
        ],
    }
}

removeIfでループしながら処理して、itemsに入っているitem_idと合致するかを調べて、合致すれば削除されます。

追加

shop_id: 20000 のitemsに新たに item_id: 600 を追加します。

POST _update_by_query

query": {
    "bool": {
        "must": [
            {
                "term": {
                    "shop_id": 10000
                }
            }
        ]
    }
},
"script": {
    "lang": "painless",
    "inline": "ctx._source.items.addAll(params.items)",
    "params"': {
        "items" => [
            {
              "item_id": 600,
              "name": アイテム600,
              "price": 3000,
            }
        ],
    }
}

本来は、データがあれば更新、データがなければ追加みたいな感じのことをやりたかったので、scriptでループ処理をして対応しようと思っていたのですが、 Painless script loop maximum number of statements limit とエラーがでてしまいまして、それならばということで、存在していれば一旦削除して、その後対象を追加していくという方式にしました。

僕の場合、上記の理由で、1リクエストで削除・追加を行うので、 conflicts=proceed をつけないと、 version_conflict_engine_exception でバージョン違いになります。

また、順番的に削除が先に行われるので、削除の方では、 refresh=wait_for もつけることで今回のやりたいことを実現してます。

終わりに

かなり手こずりましたが、なんとかなりました。

現場からは以上です。

AliceBundleでymlのfixtureでUserPasswordEncoderを使いたい

はじめに

こんばんは。

今回もSymfony5です。

かんたんな備忘録です。

hautelook/AliceBundle で ymlのfixtureを作ったさい、UserPasswordEncorderでパスワードを encodeしてみました。

github.com

本題

まず、ymlで実行する関数を定義するため、Providerを作ります。

<?php

declare(strict_types=1);

namespace App\DataFixtures\Providers;

use App\Entity\User;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

class UserPasswordEncodeProvider
{
    private UserPasswordEncoderInterface $encoder;

    public function __construct(UserPasswordEncoderInterface $encoder)
    {
        $this->encoder = $encoder;
    }

    public function encodeUserPassword(string $plainPassword): string
    {
        return $this->encoder->encodePassword(new User(), $plainPassword);
    }
}

次に、services.ymlにて、先程作ったProviderを登録します。

services:


    App\DataFixtures\Providers\UserPasswordEncodeProvider:
        tags: [ { name: nelmio_alice.faker.provider } ]

これで準備完了です。 fixtureのymlで以下のように使えるようになります。

App\Entity\User:
  user1:
    email: 'xxxxxxx@gmail.com'
    password: <encodeUserPassword(secret)>
    name: kojirock

終わりに

新しいことやると、楽しいですね。

かんたんですが以上です。

cakephp3のbootstrapでTableとるとテストが落ちちゃう

はじめに

こんばんは。

ちょっとしたハマりがありましたので、備忘録で残しておきます。

本題

cakephpでは config/bootstrap.php がかなり最初の方に呼ばれます。

それはテストでも実コードでも一緒です。

そのbootstrap上で、 $users = TableRegistry::getTableLocator()->get('Users'); みたいな感じで、テーブルをロードして、それを使おうとしている場合、コントローラのテストが落ち始めます。

例えば以下のような感じで、

<?php

$aUsers = TableRegistry::getTableLocator()->get('AUsers');
$bUsers = TableRegistry::getTableLocator()->get('BUsers');
$cUsers = TableRegistry::getTableLocator()->get('CUsers');

return [
  'Repository' => [
      'A' => new AUserRepository($aUsers),
      'B' => new BUserRepository($bUsers),
      'C' => new CUserRepository($cUsers),
  ]
];

みたいなかんじでコントローラ側でそれを使う場合に発生します。

原因

基本DBのコネクションは defaultを使うと思います。 そして、テスト時のコネクションは testを使うと思います。

cakephpのテストでは、phpunitが実行する startTest() でfixtureを読み込んでいます。 その際に、実装コードからDBに接続する場合で、テストコードからの実行の場合のみ、defaultのconnection設定をtestのものに変更するような処理が存在します。

github.com

この _aliasConnections() が実行されるより、bootstrap.phpの実行のほうがはやいので、上記のような設定を bootstrapに書くと、そこで書いている、 AUsers, BUsers ,CUsers は testコネクションに切り替える前にdefaultコネクションでインスタンス化されてしまいます。

このせいで、特定のテーブルのみ defaultコネクションをみていて、DBの取得うまくできず落ちちゃうという感じになりました。

終わりに

なかなかやらないことですが、ガッツリハマって、調べたので残しときます。

あんまり特殊なことしないほうが良いですね。

現場からは以上です。

symfony5 始めた

はじめに

こんばんは。

副業で symfony5を触る機会がありました。

人生二回目なので、忘れないうちに備忘録残しときます。

試してみたことを羅列する感じになります。

本題

1. symfony server:start

symfonyコマンドでローカルでサーバを立ち上げます。

結構ぐぐると、symfony5では使えないから phpのビルトインサーバを使えという記事が多くありました。

ただ、最新版で実行すれば問題なく立ち上げられます。

2. symfony console make:controller home

コントローラと対応するテンプレートtwigを生成します。

$ symfony console make:controller home 

 created: src/Controller/HomeController.php
 created: templates/home/index.html.twig

           
  Success! 
           

 Next: Open your new controller class and add some pages!

3. symfony console make:user

ログインで使われる User Entityを生成します。

$ symfony console make:user               

 The name of the security user class (e.g. User) [User]:
 > 

 Do you want to store user data in the database (via Doctrine)? (yes/no) [yes]:
 > 

 Enter a property name that will be the unique "display" name for the user (e.g. email, username, uuid) [email]:
 > 

 Will this app need to hash/check user passwords? Choose No if passwords are not needed or will be checked/hashed by some other system (e.g. a single sign-on server).

 Does this app need to hash/check user passwords? (yes/no) [yes]:
 > 

 created: src/Entity/User.php
 created: src/Repository/UserRepository.php
 updated: src/Entity/User.php
 updated: config/packages/security.yaml

           
  Success! 
           

 Next Steps:
   - Review your new App\Entity\User class.
   - Use make:entity to add more fields to your User entity and then run make:migration.
   - Create a way to authenticate! See https://symfony.com/doc/current/security.html

4. symfony console make:migration

databaseからマイグレーションファイルを生成します。

$ symfony console make:migration 


           
  Success! 
           

 Next: Review the new migration "migrations/Version20210514141922.php"
 Then: Run the migration with php bin/console doctrine:migrations:migrate
 See https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html

5. symfony console doctrine:migrations:migrate

作成したマイグレーションを実行します。

$ symfony console doctrine:migrations:migrate

[notice] Migrating up to DoctrineMigrations\Version20210514141922
[notice] finished in 89.4ms, used 20M memory, 1 migrations executed, 1 sql queries

6. symfony console make:auth

認証用のクラス諸々を生成します。

$ symfony console make:auth

 What style of authentication do you want? [Empty authenticator]:
  [0] Empty authenticator
  [1] Login form authenticator
 > 1

 The class name of the authenticator to create (e.g. AppCustomAuthenticator):
 >    

 Choose a name for the controller class (e.g. SecurityController) [SecurityController]:
 > 

 Do you want to generate a '/logout' URL? (yes/no) [yes]:
 > 

 created: src/Security/AppCustomAuthenticator.php
 updated: config/packages/security.yaml
 created: src/Controller/SecurityController.php
 created: templates/security/login.html.twig

           
  Success! 
           

 Next:
 - Customize your new authenticator.
 - Finish the redirect "TODO" in the App\Security\AppCustomAuthenticator::onAuthenticationSuccess() method.
 - Review & adapt the login template: templates/security/login.html.twig.

f:id:kojirooooocks:20210515031954p:plain

いい感じでできてきました。

終わりに

これからも開発続くので、第2弾もありそう。

現場からは以上です。