- 投稿日:2020-01-17T23:14:14+09:00
Laravel事始め
Laravel事始め
簡単な勤怠入力が可能なシステムを作ってみようということで去年末から色々検討した末にバックエンドをPHP/Laravelで構築することに決め、早速触ってみようということで、その流れをざっくりメモ。
記憶を頼りに書いているのでなにか間違っているかもしれない。
内容
- Windows 10にPHPとLaravelを入れる
- REST APIでGET/POSTリクエストを受付
- MySQLを読み書き
- VSCodeでデバッグする
環境
- Windows 10 (10.0.18363)
- MySQL 8.0
- PHP 7.3.8 TS x64
- Laravel 6.11.0
- VSCode 1.14.1
導入編
MySQLの導入
dev.mysql.comから
MySQL Community Server
を落としてきてインストールWorkbenchを起動して適当にDBを作成
PHPの導入
インストール
- windows.php.netから
VC15 x64 Thread Safe
を落としてきてPATHを通す- xdebug.orgから
PHP 7.3 VC15 TS (64 bit)
を落としてきてextension_dir
配下に配置設定
php.ini-development.ini
をコピーしてphp.ini
を作成
extension_dir
のコメントを外しextension_dir = "D:/php/ext"
のようにフルパスで設定
Dynamic Extensions
セクションで以下の機能を有効化
curl
mbstring
openssl
pdo_mysql
pdo_sqlite
最終行にxdebugセクションを追加
[xdebug] zend_extension="ふるぱす/php_xdebug-2.9.1-7.3-vc15-x86_64.dll" xdebug.remote_enable=1 xdebug.remote_autostart=1Laravelの導入
- getcomposer.orgからComposerを落としてきてインストール
composer global require laravel/installer
を叩くlaravel new hoge
でプロジェクトを作成php artisan serve
で起動確認VSCodeの設定
- 以下の拡張機能をインストール
PHP Debug
PHP IntelliSense
開発編
DB設定
.env
を開きDB_CONNECTION=mysql
の辺りを設定モデルの作成
DBアクセスを提供してくれるっぽい
php artisan make:model Hoge
でモデルを作成database\migrations\
にモデルが出来ているので適当なテーブルレイアウトになるように変更
- カラムの型は公式ドキュメント
php artisan migrate
でDBにテーブルが生える
- PKとか細かい設定はDB側で変える
コントローラーの作成
HTTPリクエストの受け口。勝手にルーティングしてくれる
php artisan make:controller HogeContoller -r
でコントローラーを生成- 各メソッドの動きは次のような感じっぽい。書いてないのは使ってない
index()
はコントローラーにGET投げたときに- GET: example.com/hoge/
show()
はコントローラーにパラメーター付きのGET投げたときに- GET: example.com/hoge/123
store()
はコントローラーにPOST投げたときに- POST: example.com/hoge/
- コードの頭の方に
use App\Hoge;
としてモデルを参照できるようにするルーターの調整
routes\web.php
にRoute::resource('hoge', 'HogeController');
を追加。これで/hoge
をHogeController
に割当できるようになるコントローラーの調整
DBの内容を全件返す
return response(Hoge::all());
DBの内容をフィルタして返す
return response(Hoge::all()->where('カラム名', '=', $id));
DBにデータを登録する
Hoge::create([ "id": "1", "name": "hogeyama hoge" ]);
このときモデル
app\Hoge.php
で登録したいフィールドを許可する必要があるので、以下のように実装
protected $fillable= array('id', 'name');
トラブルシュート
POSTするとpage expired
routes\web.php
にRoute::resource('hoge', 'HogeController');
を追加した上でstore()
にPOSTの処理を書くこと
- 因みにこれはCSRF関係のエラー
HTTP RESPONSE STATUSが500
- 実装がなんかおかしいのでデバッグして確認
Failed loading Zend extension 'php_xdebug-
- xdebugのDLLバージョンやTSの有無が違う。PHPのバージョンやビルドと一致したものにする
指定されたモジュールが見つかりません。
と出ている場合は絶対パスで設定するto fillable property to allow mass assignment
- モデルの
$fillable
でDBに登録許可する対象項目を設定する
protected $fillable= array('id', 'name');
- 投稿日:2020-01-17T17:24:26+09:00
MySQL Workbenchでupdate文を実行したら「Error Code: 1175」のエラーが出た
はじめに
MySQL Workbenchにて、update文を実行したら、以下のエラーが出たので対処法を残します
Error Code: 1175. You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column. To disable safe mode, toggle the option in Preferences -> SQL Editor and reconnect. 0.0017 sec環境
MySQL Workbench 8.0
結論
「Safe Updates」の設定をオフにすることで、エラーはでなくなります
設定方法
MySQL Workbenchを起動して、メニューを以下の順で選択します
- 「MySQLWorkbench」→「Preferences」
左のメニューの「SQL Editor」を選択して、一番下までスクロールすると「Safe Updates」の設定があるので、チェックをはずして、「OK」ボタンをクリックします(以下イメージ参照)
- データベースに再接続すれば、設定が有効になり、update文が実行できます
まとめ
設定を変更することで、update文が実行できるようになりますが、オフにすることでwhere句なしで更新できてしまうので、本番環境にアクセスするときは気をつけましょう
設定をオンにしておくとよいかと思います
- 投稿日:2020-01-17T16:40:11+09:00
[Rails,位置情報] activerecord-mysql2spatial-adapterの使い方
しずおかオンラインでWeb開発しているkazuomatzです。
Rails + MySQLで位置情報を扱う需要は、少なからずあるかと思います。
Rails + MySQLで位置情報を扱う場合、activerecord-mysql2spatial-adapterが定番のGemかと思うのですが、このGemの開発が止まっていて、Railsのバージョンアップのたびに、有志の開発者の方が本家からBranchしたバージョンを独自リリースしている状態というエントリーを記事にしたのですが、あまり反響がなかったので、そういう需要はもはやないのかなと思っておりました。
そんな中、この記事を見た方から、そもそもactiverecord-mysql2spatial-adapterの使い方がまとめれているところがあまりないという問い合わせがあったので、簡単に書き留めておきます。
こちらの記事に、Rails 4.2, 5.0, 5.1, 6.0に対応したGemfileの書き方を記述してあります(Rails6.0の対応については、僕がやりました)。こちらを参照にGemfileを記述して、bundle installします。
config/database.yml
adapterをmysql2spatialにする。ここがポイントです。
config/database.ymldefault: &default adapter: mysql2spatial pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: xxxx password: xxxxx socket: /tmp/mysql.sock : : :マイグレーションファイル
位置情報を含むモデルを作成するためのマイグレーションファイルです。
t.point (t.geometry でもOK)型として宣言します。nullは許容しないようにします。
また、インデックスを作成する場合は、spatial: trueとします。MySQL 5.6以前の場合、spatial: trueの指定をする場合は、ストレージエンジンがMyISAMである必要があります。MySQL 5.7以降であればInnoDBでOKです。
db/migrate/xxxxxxxxxxxxx.rbclass CreateLocations < ActiveRecord::Migration[6.0] def change create_table :locations do |t| t.string :name, comment: 'スポット名称' t.point :geom, null: false, comment: '緯度経度' end add_index :locations, :geom, spatial: true endPointクラスを作る
Geometry型に緯度経度を設定するために便利なPointクラスを作っておきます。
Point.rb# Geometry型に緯度経度を設定するためのPointクラス class Point def self.from_x_y(x, y) x.present? && y.present? ? "POINT(#{x} #{y})" : nil end end緯度経度の設定と参照
あとは、以下のようにすれば、設定と参照が可能になります。
location.rblocation = Location.new location.name = '東京駅' location.geom = Point.from_x_y( 139.767125, 35.681236 ) location.save! # 緯度経度参照 p location.geom.x # -> 139.767125 p location.geom.y # -> 35.681236
- 投稿日:2020-01-17T11:56:15+09:00
SpringSecurityで認証機能を実装③
前回の続きです。
※ここからたくさんのファイルを作成していきます。失敗した際に、途中で後戻りが出来なくなる可能性が高いので、ここまでプロジェクト情報をコピーまたはGithubなどに挙げておくといいでしょう。
私はこれをせず何度もプロジェクト作成してかなりの時間を要しました。5、SpringSecurityの認証機能の実装。
・ここからがメインディッシュになります。
前回までの実装がうまく行ってないとエラー対応が面倒なので、ここまでを完璧に実装しておきましょう。5-1,POMを編集。
・まずSpringSecurityを導入するためMavenの依存関係を記載してある、pom.xmlを書き換えます。またDBから受け取ったデータを格納するエンティティクラスを使用するためjavax.persistenceも導入します。
pom.xml<!-- SpringSecurity --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- Thyemeleaf拡張(セキュリティ) --> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/javax.persistence/javax.persistence-api --> <!-- Entityのアノテーションを使うため --> <dependency> <groupId>javax.persistence</groupId> <artifactId>javax.persistence-api</artifactId> <version>2.2</version> </dependency> <!-- JpaRepository--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>・コードを書いただけだと反映されません。Mavenプロジェクト更新する必要があります。
「プロジェクト右クリック」→「Maven」→「プロジェクトの更新」でMavenファイルに更新をかけます。選択したファイルが正しければ「OK」で更新をかけましょう。
・アプリを実行した際にコンソール上にエラーが出なければOKです。
5-2,認証機能を実装する。
・最後です。最後にして、たくさんのファイルを作成していきます。ファイル構成イメージは以下になります。赤枠が新規作成、黄色枠が編集するファイルです。
・また処理の流れもイメージにしましたので、参考にして下さい。(非常に見づらくて申し訳ない・・・)
・コードを載せます。コメントをたくさん書いていますが、正直全てを理解するのが難しく間違っている可能性もあります。各自で調べて理解を深めて頂くと幸いです。
SpringLogin.app.config/WebSecurityConfig.javapackage SpringLogin.app.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import SpringLogin.app.service.UserDetailsServiceImpl; /** * SpringSecurityを利用するための設定クラス * ログイン処理でのパラメータ、画面遷移や認証処理でのデータアクセス先を設定する * @author aoi * */ @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { // UserDetailsServiceImplのメソッドを使えるようインスタンス化しておきます。 @Autowired private UserDetailsServiceImpl userDetailsService; //フォームの値と比較するDBから取得したパスワードは暗号化されているのでフォームの値も暗号化するために利用 @Bean public BCryptPasswordEncoder passwordEncoder() { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); return bCryptPasswordEncoder; } /** * 認可設定を無視するリクエストを設定 * 静的リソース(image,javascript,css)を認可処理の対象から除外する */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers( "/images/**", "/css/**", "/javascript/**" ); } /** * 認証・認可の情報を設定する * 画面遷移のURL・パラメータを取得するname属性の値を設定 * SpringSecurityのconfigureメソッドをオーバーライドしています。 */ @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") //ログインページはコントローラを経由しないのでViewNameとの紐付けが必要 .loginProcessingUrl("/login") //フォームのSubmitURL、このURLへリクエストが送られると認証処理が実行される .usernameParameter("username") //リクエストパラメータのname属性を明示 .passwordParameter("password") .defaultSuccessUrl("/userList", true) //認証が成功した際に遷移するURL .failureUrl("/login?error") //認証が失敗した際に遷移するURL .permitAll() //どのユーザでも接続できる。 .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login?logout") .permitAll(); } /** * 認証時に利用するデータソースを定義する設定メソッド * ここではDBから取得したユーザ情報をuserDetailsServiceへセットすることで認証時の比較情報としている * @param auth * @throws Exception * AuthenticationManagerBuilderは認証系の機能を有している。 * userDetailsServiceもその一つでフォームに入力されたユーザが使用可能か判断します。 * https://docs.spring.io/spring-security/site/docs/4.0.x/apidocs/org/springframework/security/config/annotation/authentication/builders/AuthenticationManagerBuilder.html */ @Autowired public void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); /* * インメモリの場合は以下を使います。 auth .inMemoryAuthentication() .withUser("user").password("{noop}password").roles("USER"); */ } }SpringLogin.app.servise/UserDetailsServiceImpl.javapackage SpringLogin.app.service; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import SpringLogin.app.repository.LoginUserDao; import SpringLogin.app.entity.LoginUser; /** * Spring Securityのユーザ検索用のサービスの実装クラス * DataSourceの引数として指定することで認証にDBを利用できるようになる * @author aoi * */ @Service public class UserDetailsServiceImpl implements UserDetailsService{ //DBからユーザ情報を検索するメソッドを実装したクラス @Autowired private LoginUserDao userDao; /** * UserDetailsServiceインタフェースの実装メソッド * フォームから取得したユーザ名でDBを検索し、合致するものが存在したとき、 * パスワード、権限情報と共にUserDetailsオブジェクトを生成 * コンフィグクラスで上入力値とDBから取得したパスワードと比較し、ログイン判定を行う */ @Override public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { LoginUser user = userDao.findUser(userName); if (user == null) { throw new UsernameNotFoundException("User" + userName + "was not found in the database"); } //権限のリスト //AdminやUserなどが存在するが、今回は利用しないのでUSERのみを仮で設定 //権限を利用する場合は、DB上で権限テーブル、ユーザ権限テーブルを作成し管理が必要 List<GrantedAuthority> grantList = new ArrayList<GrantedAuthority>(); GrantedAuthority authority = new SimpleGrantedAuthority("USER"); grantList.add(authority); //rawDataのパスワードは渡すことができないので、暗号化 BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); //UserDetailsはインタフェースなのでUserクラスのコンストラクタで生成したユーザオブジェクトをキャスト UserDetails userDetails = (UserDetails)new User(user.getUserName(), encoder.encode(user.getPassword()),grantList); return userDetails; } }SpringLogin.app.repository/LoginUserDao.javapackage SpringLogin.app.repository; import javax.persistence.EntityManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import SpringLogin.app.entity.LoginUser; /** * DBへのアクセスメソッドを呼び出すDao * @author aoi * */ @Repository public class LoginUserDao { /** * エンティティを管理するオブジェクト。 * 以下のメソッドでエンティティクラスであるLoginUserにキャストして戻り値を返すので必要なオブジェクト。 */ @Autowired EntityManager em; /** * フォームの入力値から該当するユーザを検索 合致するものが無い場合Nullが返される * @param userName * @return 一致するユーザが存在するとき:UserEntity、存在しないとき:Null */ public LoginUser findUser(String userName) { String query = ""; query += "SELECT * "; query += "FROM user "; query += "WHERE user_name = :userName "; //setParameterで引数の値を代入できるようにNamedParameterを利用 //EntityManagerで取得された結果はオブジェクトとなるので、LoginUser型へキャストが必要となる return (LoginUser)em.createNativeQuery(query, LoginUser.class).setParameter("userName", userName).getSingleResult(); } }SpringLogin.app.entity/LoginUser.javapackage SpringLogin.app.entity; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; /** * ログインユーザのユーザ名、パスワードを格納するためのEntity * @author aoi * */ @Entity @Table(name = "user") public class LoginUser { @Column(name = "user_id") @Id private Long userId; @Column(name = "user_name") private String userName; @Column(name = "password") private String password; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }SpringLogin.app.repository/UserRepository.javapackage SpringLogin.app.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import SpringLogin.app.entity.LoginUser; /* * Spring Frameworkのデータ検索を行うための仕組み。 * DIに登録しておくことでデータ検索が可能になる。引数には<エンティティクラス, IDタイプとなる> * https://www.tuyano.com/index3?id=12626003 */ @Repository public interface UserRepository extends JpaRepository<LoginUser, Integer>{ }SpringLogin.app.config/SpringLoginApplication.javapackage SpringLogin.app.config; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.ComponentScan; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootApplication //Spring Bootアプリケーションであることを示す @ComponentScan("SpringLogin.app") //BeanとしてDIに登録する。パッケージとして指定することができる。 @EntityScan("SpringLogin.app.entity") //上記同様BeanとしてDIに登録。 @EnableJpaRepositories("SpringLogin.app.repository") //JpaRepositoryをONにするためのもの。指定されたパッケージ内を検索し、@Repositoryを付けたクラスをBeanとして登録。 public class SpringLoginApplication { public static void main(String[] args) { SpringApplication.run(SpringLoginApplication.class, args); } }・お疲れ様です。だいぶ長いコードになりましたがあともう一踏ん張りです!では、ここまでコードが書けたら、ブラウザで確認しましょう。
5-3,動作確認。
まずここにアクセスします→http://localhost:8080/
するとURLが/loginの画面に遷移します。
これはSpringSecurityのが適用されている証拠で、WebSecurityConfig.javaのauthorizeRequestsメソッド内にはloginしていない状態でもアクセスできるURLを指定します。ですが今回は何も指定がないので、未ログイン状態ではログインページ以外アクセスが出来ません。なのでlocalhost:8080/にアクセスした際に/loginにリダイレクトされます。・上のイメージの様に動いていたらOKです。
・次に認証機能を確認します。
まず間違ったパスとユーザでログインします。
そうするとログインできない&"/login?error"にリダイレクトされます。
これはWebSecurityConfig.jav内のfailureUrl()に影響されています。・上のイメージの様に動いていたらOKです。
次に正規のユーザとパスでログインします。今回はユーザを2つ(user1, pass1+)(yama, kawa)作成してありますがどちらでもOKです。
・上のイメージの様に動いていたらOKです。
まとめ
これで以上になります。長らくお付き合いありがとうございました。認証機能、MySQLの使い方は何とか理解できたかと思います。私自身まだ理解しきれていないところもあるので間違っていたり、足りない記述がありましたら是非コメント下さい
感想として正直Rails上がりと言うのもあり、少しなめていました。と言うのもRailsにはdeviceがあったのでログイン機能の実装には苦労しなかったからです。SpringSecurityは桁違いに難しかったです...
いろいろ記事を探し回ったのですが、これ本当に理解できて、まとめている人いるのかなぁ?と感じています。
今回理解仕切れてない状態で投稿する事に迷いましたが、自分と同じ様に悩んでる人の為にも早く投稿したかったのでこのような状態で投稿させていただきました!
自分もまだまだなのでもっとスキル磨きたいと思います!ありがとうございました。
- 投稿日:2020-01-17T10:52:08+09:00
SpringSecurityで認証機能を実装②
前回の続きです。
4、ログイン画面の実装。
・今回はログイン画面と実装します。ただ認証機能は実装しないので、ログイン画面からボタンを押せばフォームの内容に限らず画面を遷移させます。
・ファイルの構成イメージは以下になります。コメントをズラーと書いてしまったので不要な場合は消して下さい。
・コードは以下になります。
SpringLogin.app.controller/LoginController.javapackage SpringLogin.app.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; //import SpringLogin.app.service.UserService; @Controller public class LoginController { // @Autowired // UserService userService; @GetMapping("/login") public String getSignUp(Model model) { return "login"; } //FormのSubmitを押すとPostメソッドがリクエストされます。()に書かれたURLのリクエストを受け取るとこのメソッドが発動します。 @PostMapping("/login") public String postSignUp(Model model) { /* 今回はリダイレクトを使います。画面遷移などファイル間をまたぐ場合はリダイレクト使います。 イメージ的にはGetメソッドを呼び出していると考えていいでしょう。 試しにリダイレクトせず通常のフォワード(return "xxx";)とすると画面自体は表示されますがURLが変わりません。 こうすると、遷移先で受け取りたいデータなどが受け取れないことがあるので、リダイレクトを使用したほうが良いでしょう。 */ return "redirect:/userList"; } }:templates/login.html <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"></meta> </head> <body> <!-- コンテンツ部分 --> <div> <!-- method="post"にすることでpostリクエストを発行することができます。th:actionでPostを処理するメソッドをURLで指定します。 --> <form method="post" th:action="@{/login}"> <div> <label>Username:</label> <!-- 今回の実装では使いませんがname属性を指定してあります。後で認証の時に使います。 --> <input type="text" name="username" /> </div> <div> <label>Password:</label> <input type="password" name="password" /> </div> <button type="submit">login</button> </form> </div> </body> </html>・コードが書けたら、ブラウザでアプリを確認しましょう。
URLが変わります→http://localhost:8080/login
ページが表示されたら、Loginボタンを押下してuserListに遷移することを確認します。この時URLが変わっていることを確認しましょう。・動画の様に遷移できていればOKです!
次からはこの記事のメインになります。ここもボリュームが大きくなりそうなのでいったん切ります。続きはこちら。