たけるのプログラミング

作ったものとか、気ままにアップします。

【PHP】名前空間とエイリアスについて再確認の巻

参考
【PHP超入門】名前空間(namespace・use)について - Qiita
PHP: 名前空間 - Manual


以下のようなコードだとエラーが起きる

try.php

<?php
require_once 'lebron.php';
require_once 'curry.php';

shoot();

lebron.php

<?php
function shoot()
{
    echo '2ポイントシュート';
}

curry.php

<?php
function shoot()
{
    echo '3ポイントシュート';
}

shoot()と言われてもlebronのshootか、curryのshootか分からない(名前の衝突が起こっている)のでエラーとなる。なので名前空間を利用してエラーを回避する。

try.php

<?php
require_once 'lebron.php';
require_once 'curry.php';

lebron\shoot();
curry\shoot();

lebron.php

<?php
namespace lebron;

function shoot()
{
    echo '2ポイントシュート';
}

curry.php

<?php
namespace curry;

function shoot()
{
    echo '3ポイントシュート';
}


こんな感じにディレクトリ感覚で指定もできる。
try.php

<?php
namespace nba;

require_once 'lebron.php';
require_once 'curry.php';

//修飾形式
lakers\forward\shoot();
warriers\guard\shoot();

//完全修飾形式
\nba\lakers\forward\shoot();
\nba\warriers\guard\shoot();

lebron.php

<?php
namespace nba\lakers\forward;

function shoot()
{
    echo '2ポイントシュート';
}

curry.php

<?php
namespace nba\warriers\guard;

function shoot()
{
    echo '3ポイントシュート';
}


また例えば名前空間が長い時
lebron.php

<?php
namespace sports\basketball\nba\lakers\forward;

function shoot()
{
    echo '2ポイントシュート';
}

try.php

<?php
require_once 'lebron.php';
require_once 'curry.php';

sports\basketball\nba\lakers\forward\shoot();

長い名前空間の時いちいち書くのがめんどくさいので、useを使ってエイリアスを作成し楽をする。

<?php
require_once 'lebron.php';
require_once 'curry.php';

//やり方①
use sports\basketball\nba\lakers\forward as king;
//やり方②
use sports\basketball\nba\warriers\guard;

king\shoot();
guard\shoot();

【PHP】ファイル操作そんなに勉強したことなかったので、基本を勉強する

ここでのファイルポインタとは

  • ファイルを操作するための変数
  • ファイルを編集するための現在位置

fopen

PHP: fopen - Manual
ファイルまたはurlを開く。返り値はファイルポインタを返す。失敗した場合はfalseを返す。

fclose

PHP: fclose - Manual
ファイルを閉じる

fgets

PHP: fgets - Manual
ファイルから1行取得する
引数にファイルポインタを指定し、そのファイルから一行づつデータを取得し、文字列として返す。読み込むデータがない場合やエラーが起こった場合にfalseを返す。

<?php
//開ける
$fp = fopen('test.txt','r');
echo fgets($fp);
//閉じる
fclose($fp);
?>

fwrite

PHP: fwrite - Manual

ファイルに書き込む

<?php
//modeにwを指定
$fp = fopen('test.txt','w');
fwrite($fp,'書き込み!');
fclose($fp);
?>

ファイルに追加で書き込む

<?php
//modeにaを指定
$fp = fopen('test.txt','a');
fwrite($fp,'追加で書き込み!');
fclose($fp);
?>

file_get_contents

PHP: file_get_contents - Manual

ファイルの全ての内容を取得する。返り値は読み込んだデータ。失敗した場合はfalseを返す。PHPは動的型付けのため失敗しなかった場合でも==を使うと思わぬ挙動となる可能性があるため条件分岐を行う際には===を使用する。

<?php
$data = file_get_contents('test.txt');
//一行ずつ文字列を配列に代入
$array = explode("\n",$data);
var_dump($array);
//出力結果
//array(2) { [0]=> string(9) "一行目" [1]=> string(9) "二行目" }
?>

file_put_contents

PHP: file_put_contents - Manual

文字列をファイルに書き込む。この関数はfopen()、fwrite()、fclose()を使ってファイルに書き込むのと同じ。

<?php
//文字列パターン
file_put_contents('test.txt','こんにちは!');
//配列パターン
file_put_contents('test.txt',[1,2,3,4,5]);
//12345 となる
//このコードだと上書きされてしまう。
?>

fgetcsv

PHP: fgetcsv - Manual

csvファイルから1行ずつ取得する。返り値は,で区切った配列。

<?php
$fp = fopen('test.csv','r');
var_dump(fgetcsv($fp));
var_dump(fgetcsv($fp));
//array(3) { [0]=> string(6) "lebron" [1]=> string(4) "melo" [2]=> string(4) "wade" } array(3) { [0]=> string(4) "bosh" [1]=> string(5) "mario" [2]=> string(4) "cole" }
fclose($fp);
?>

fputcsv

PHP: fputcsv - Manual

配列をcsv形式にフォーマットし、ファイルポインタに書き込む。第二引数には文字列の配列を指定する。

<?php
$fp = fopen('test.csv','w');
fputcsv($fp,['a','b','c']);
fclose($fp);
?>

【PHP】クッキーとセッション勉強会

クッキー(cookie)とは

Webブラウザにデータを保存するためのファイルのこと、Webブラウザに保存されるデータのこと

セッション

ユーザーが行う一連の操作のこと 例えば ログイン-> ............ ->ログアウト の流れ

使われる場面

よくある例えだが、ショッピングサイトでカートに商品を入れ、ページを開いているタブを閉じ、再び開きカートをみると先ほど入れた商品が入っているという仕組みはクッキーが活用されている。

そもそもなぜcookieが必要なのか

ステートレス

通信プロトコルであるHTTPはステートレスつまり状態を保持することができないため、cookieを利用してセッションを管理する必要がある。

プログラムを書いてみる

ページにアクセスした際に、countを1プラスするプログラムを作成する。

①setcookieを利用したパターン

PHP: setcookie - Manual

<?php
if(!isset($_COOKIE['count'])){
    setcookie('count',0);
    echo '初めてですね!';
}else{
    setcookie('count',$_COOKIE['count'] += 1);
    echo $_COOKIE['count'].'回目の更新ですね!';
}
?>

最初にページを開くと '初めてですね!' と表示され、ページを更新するごとにcountがプラス1されて画面に表示されます。
ブラウザでcookieを確認するには(Chrome)の場合は開発者ツールを開き、アプリケーション→Cookieから確認することができます。
f:id:takeru232423:20220302205612p:plain



またHttpヘッダを確認することにより、Cookieが設定されているかも確認することができます。Chromeの場合、開発者ツールからネットワーク→左の名前という部分からURLを選択し、ヘッダーという部分で確認することができます。1回目のアクセスを行うとレスポンスヘッダーに Set-Cookie: count=0 という部分があります。これはサーバーからブラウザに対してcount=0を保存するよう指示を出していることを意味しています。
HTTP Cookie の使用 - HTTP | MDN


setcookieに関して以下の記事を読むと、セキュリティーに関して考えなければいけないことがわかる。
setcookie()っていつ使うの? - Qiita


②session_startを利用したパターン(cookieとしてセッションIDをブラウザに保存する)

PHP: session_start - Manual

<?php
session_start();
if(!isset($_SESSION['count'])){
    $_SESSION['count'] = 0;
    echo '初めてですね!';
}else{
    $_SESSION['count']++;
    echo $_SESSION['count'].'回目の更新ですね!';
}
?>
流れ
  1. ユーザーがページにアクセス(リクエスト)
  2. session_start()によりセッションを開始し、セッションIDを生成し、レスポンスヘッダーにてCookieに保存するように指示を出す。セッションIDによりサーバーはユーザーを識別することができる。(レスポンス)
  3. 2回目のリクエストの際にリクエストヘッダにセッションIDを含めてアクセス(リクエスト)
  4. セッションIDに基づいた値などを使い処理を行い返す(レスポンス)

あとは3、4の繰り返し。①と同様の方法でヘッダーの確認が可能。①のやり方と大きな違いはデータ自体をCookieに保存していないこと。①ではcountとその値をCookieに保存していたが、②ではセッションIDだけをCookieに保存している。countとその値はセッションファイルに保存されている。なのでセッションIDをもとにセッションファイルから$_SESSIONというグローバル変数を使って値を取得しているということ。
またこのセッションIDが他人にバレてしまうと、個人情報流失してしまう恐れがある。(セッションハイジャック)以下の動画が面白かった。


【PHP】データベース周りをフレームワークに頼りすぎていたのでPDOについてもう一度復習する【その1】

optionなど詳しい仕様はドキュメントを参照
PHP: PDO - Manual
PHP: PDOStatement - Manual


PDOオブジェクトを生成する

PHP: PDO::__construct - Manual

<?php
//データソース
$dsn = 'mysql:dbname=pdo_test;host=localhost;charset=utf8';
//ユーザー名
$user = 'root';
//パスワード
$password = 'root';
//第4引数にオプションも追加することができる
try {
$db = new PDO($dsn,$user,$password);
}catch(PDOException $e){
    echo $e->getMessage();
}
?>

PDO::exec

PHP: PDO::exec - Manual

<?php
$exec_count = $db->exec('INSERT INTO members SET name="DwightHoward", number=39, position="center", age=36');
//1と表示される
echo $exec_count;
?>
  • SQLを実行し、返り値として更新削除された行数を返す
  • SELECT文は対応していない
  • SELECT文を実行したいなら、PDO:query()PDO:prepare()とその返り値であるPDOSTatementオブジェクトのexecute()を利用する

PDO::query

PHP: PDO::query - Manual

<?php
//PDOStatementオブジェクトを取得
$pdo_stm = $db->query('SELECT * FROM members');
//PDOStatementオブジェクトのfetchメソッドを使用
var_dump($pdostm->fetch());
?>
  • 返り値はPDOStatementオブジェクト、失敗したらfalseを返す
  • プレースホルダを指定しない時(SQLが固定)に使用する。
  • 複数回SQL文を実行したい時SQL文にプレースホルダがある時は、PDO:prepare()とその返り値であるPDOStatementオブジェクトのexecute()
PDOStatement::fetch

PHP: PDOStatement::fetch - Manual

  • SQLの実行結果セットの次の一行を取得する

さっきのvar_dumpの出力結果のfetch()をfetchAll()に変えると以下のような返り血となる

array(10) {
  ["id"]=>
  string(1) "1"
  [0]=>
  string(1) "1"
  ["name"]=>
  string(11) "LebronJames"
  [1]=>
  string(11) "LebronJames"
  ["number"]=>
  string(1) "6"
  [2]=>
  string(1) "6"
  ["position"]=>
  string(7) "forward"
  [3]=>
  string(7) "forward"
  ["age"]=>
  string(2) "37"
  [4]=>
  string(2) "37"
}

つまり

var_dump($pdostm->fetch());
var_dump($pdostm->fetch());

と実行すると

array(10) {
  ["id"]=>
  string(1) "1"
  [0]=>
  string(1) "1"
  ["name"]=>
  string(11) "LebronJames"
  [1]=>
  string(11) "LebronJames"
  ["number"]=>
  string(1) "6"
  [2]=>
  string(1) "6"
  ["position"]=>
  string(7) "forward"
  [3]=>
  string(7) "forward"
  ["age"]=>
  string(2) "37"
  [4]=>
  string(2) "37"
}
array(10) {
  ["id"]=>
  string(1) "2"
  [0]=>
  string(1) "2"
  ["name"]=>
  string(14) "CarmeloAnthony"
  [1]=>
  string(14) "CarmeloAnthony"
  ["number"]=>
  string(1) "7"
  [2]=>
  string(1) "7"
  ["position"]=>
  string(7) "forward"
  [3]=>
  string(7) "forward"
  ["age"]=>
  string(2) "37"
  [4]=>
  string(2) "37"
}
PDOStatement::fetchAll

PHP: PDOStatement::fetchAll - Manual

  • SQLの実行結果セットの残りの行すべてを取得
array(5) {
  [0]=>
  array(10) {
    ["id"]=>
    string(1) "1"
    [0]=>
    string(1) "1"
    ["name"]=>
    string(11) "LebronJames"
    [1]=>
    string(11) "LebronJames"
    ["number"]=>
    string(1) "6"
    [2]=>
    string(1) "6"
    ["position"]=>
    string(7) "forward"
    [3]=>
    string(7) "forward"
    ["age"]=>
    string(2) "37"
    [4]=>
    string(2) "37"
  }
  [1]=>
  array(10) {
    ["id"]=>
    string(1) "2"
    [0]=>
    string(1) "2"
    ["name"]=>
    string(14) "CarmeloAnthony"
    [1]=>
    string(14) "CarmeloAnthony"
    ["number"]=>
    string(1) "7"
    [2]=>
    string(1) "7"
    ["position"]=>
    string(7) "forward"
    [3]=>
    string(7) "forward"
    ["age"]=>
    string(2) "37"
    [4]=>
    string(2) "37"
  }
  [2]=>
  array(10) {
    ["id"]=>
    string(1) "3"
    [0]=>
    string(1) "3"
    ["name"]=>
    string(12) "YutaWatanabe"
    [1]=>
    string(12) "YutaWatanabe"
    ["number"]=>
    string(2) "18"
    [2]=>
    string(2) "18"
    ["position"]=>
    string(7) "forward"
    [3]=>
    string(7) "forward"
    ["age"]=>
    string(2) "27"
    [4]=>
    string(2) "27"
  }
  [3]=>
  array(10) {
    ["id"]=>
    string(1) "4"
    [0]=>
    string(1) "4"
    ["name"]=>
    string(9) "ChrisPaul"
    [1]=>
    string(9) "ChrisPaul"
    ["number"]=>
    string(1) "3"
    [2]=>
    string(1) "3"
    ["position"]=>
    string(5) "guard"
    [3]=>
    string(5) "guard"
    ["age"]=>
    string(2) "37"
    [4]=>
    string(2) "37"
  }
  [4]=>
  array(10) {
    ["id"]=>
    string(1) "5"
    [0]=>
    string(1) "5"
    ["name"]=>
    string(12) "DwightHoward"
    [1]=>
    string(12) "DwightHoward"
    ["number"]=>
    string(2) "39"
    [2]=>
    string(2) "39"
    ["position"]=>
    string(6) "center"
    [3]=>
    string(6) "center"
    ["age"]=>
    string(2) "36"
    [4]=>
    string(2) "36"
  }
}

PDO::prepare と PDOStatement::execute

<?php
//ユーザーがnameにYutaWatanabeを指定したとする。
//YutaWatanabeに関する行を取得する。
$player_name = "YutaWatanabe";

//PDOStatementオブジェクトを取得、SQL文を"準備"
$pdo_stm = $db->prepare('SELECT * FROM members WHERE name = ? ');

//PDOStatementオブジェクトのexecuteメソッドを"実行"
//?にYutaWatanabeをバインドする
$isSuccess = $pdo_stm->execute(array($player_name));

//取得したデータを表示してみる
var_dump($pdo_stm->fetch());
//実行が成功したか表示してみる
var_dump($isSuccess);
?>

実行結果

array(10) {
  ["id"]=>
  string(1) "3"
  [0]=>
  string(1) "3"
  ["name"]=>
  string(12) "YutaWatanabe"
  [1]=>
  string(12) "YutaWatanabe"
  ["number"]=>
  string(2) "18"
  [2]=>
  string(2) "18"
  ["position"]=>
  string(7) "forward"
  [3]=>
  string(7) "forward"
  ["age"]=>
  string(2) "27"
  [4]=>
  string(2) "27"
}
bool(true)
PDO::prepare 準備

PHP: PDO::prepare - Manual

  • 複数回実行されるSQL文やプレースホルダがある時に有効
  • SQLインジェクションからの保護に有効
  • 返り値はPDOStatementオブジェクト、falseまたはPDOException
  • SQL文にて 名前付きパラメータや?パラメータを使用することができる
PDOStatement::execute 実行

PHP: PDOStatement::execute - Manual

  • prepareのプレースホルダ部分をバインドするために、引数として配列を渡す。他にbindParam()やbindValue()を使用する方法もある。
  • 返り値は成功した時にtrue、失敗した時にfalseを返す

【日能研 個人情報流出】SQLインジェクションについて解説

最近よくフィッシングメールといったいわゆるサイバー犯罪が増えてきました。

そして最近話題になったのが日能研SQLインジェクションの被害にあり、個人情報を28万件流出させてしまった事件。
www2.nichinoken.co.jp


xtech.nikkei.com


SQLインジェクションって何?と思う方も多いと思うので簡単なプログラムと共に説明したいと思います。

まずSQLインジェクションSQLとはデータベースを操作するための言語です。

データベースは情報を記録する箱というイメージ。

この箱であるデータベースに対して、SQLを使ってデータを追加したり、読み取ったり、更新、削除します。

SQLインジェクションは、このSQLを悪用して情報を入手したり、不正ログインを可能にします。

具体例

今回はそんなSQLインジェクションの被害に遭うような全くセキュリティ対策がされていないコードを書いてみました。

名前とパスワードを入力してログインするだけのシステム。

この後実際にSQLインジェクションを行い、パスワードなしでログインしてみます。

用意するデータベースのテーブルはこんな感じ
f:id:takeru232423:20220203210434p:plain

nameとpasswordが合致したら

f:id:takeru232423:20220203210746p:plain

ログイン成功画面にリダイレクトする仕組み↓。

f:id:takeru232423:20220203211145p:plain

ログイン処理で使うSQL

SELECT * FROM student where name ='$name' and password = '$password'

例) ユーザーがnameに山田太郎、passwordにtaroyamadaと入力した際にプログラム上で実行されるSQL
SELECT * FROM student where name ='山田太郎' and password = 'taroyamada'


この$nameと$passwordには上記画像のフォームからユーザーが入力した値が入ります。
whereとは条件を指定する際に使い、andはかつという意味で、データベースのnameの値かつpasswordの値が一致する行を探し、その行を返します。
つまり行の情報が返されたらログインが成功というわけです。

実際にSQLインジェクションを行う

名前に山田太郎、そしてパスワードの部分に' OR '1' = '1と入力
f:id:takeru232423:20220203212512p:plain

ログインボタンを押すと、、、
f:id:takeru232423:20220203212541p:plain

ログイン成功画面が出てきました。

パスワードが全然違うのに何故でしょう。

この時実行されるSQL

SELECT * FROM student where name ='山田太郎' and password = '' OR '1' = '1'

このORは'または'という意味。ANDは'かつ'という意味。つまり上記のSQL文は

名前が山田太郎かつパスワードが''  または'1'='1'の時はログイン成功とするという意味になります。

このようにSQLの知識が少しあれば、誰でもSQLインジェクションを起こすことが可能です。

このようなSQLインジェクションを防ぐためには、プログラム側でユーザーの入力した値に対してエスケープ処理など様々な対策を行わなくてはいけません。

今回のセキュリティー対策がされていないコード

phpで書きました

//ログイン処理
if($_POST['name']!=''&&$_POST['password']!=''){
    //入力された値を変数に入れる
    $name = $_POST['name'];
    $password = $_POST['password'];

    //ログイン処理
    //ログインが2つの値と一致する行があった場合にログイン成功とする

    //不正ログイン
    // ' OR '1' = '1
    $count = $db->query("SELECT * FROM student where name ='$name' and password = '$password' ");
    
    if($count->fetch()){
        header('Location:userpage.php');
        exit();
    }else{
        echo 'ログイン不成功!';
    }
}

?>

<form action="" method='POST'>
    名前:<input type="text" name="name">
    パスワード:<input type="text" name="password">
    <input type="submit" value="ログイン">
</form>

感想

SQLインジェクション恐るべし、、、

【Laravel】Modelのリレーション1人勉強会①【PHP】

ER図は以下のツールを使いました。とても便利でした。無料です。
Flowchart Maker & Online Diagram Software

1対1のリレーション hasOne()

イメージはこんな感じ
f:id:takeru232423:20211115100327p:plain

Tweet_userモデルでTweet_contentを用意する

public function Tweet_content()
    {
        return $this->hasOne('App\Tweet_content');
    }

このようにすることでプロパティにアクセスする感覚でリレーション先の値を得ることができる。
Tweet_content()と定義したが、以下のように()はいらない。

Tweet_user::find(1)->Tweet_content->tweet






1対 多 hasMany()

イメージはこんな感じ
f:id:takeru232423:20211115101116p:plain

ユーザーが複数のツイートをしている状態。

Tweet_userモデルでTweet_contentを用意する。

 public function Tweet_content()
    {
        return $this->hasMany('App\Tweet_content');
    }

controllerで以下のようにして

$data = Tweet_user::find(1);
        return view('home',compact('data'));

view側でこんな感じで利用できる。

@foreach ($data->Tweet_content as $item)
<p>{{ $item->tweet }}</p>
@endforeach






多 対 1 belongsTo()

イメージはこんな感じ
f:id:takeru232423:20211115103948p:plain

Tweet_contentモデルでTweet_userを用意する。

public function Tweet_user()
    {
        return $this->belongsTo('App\Tweet_user');
    }

ツイートからツイートした人の名前を取得することができるようになる。

Tweet_content::find(1)->Tweet_user->name

【Laravel】ファイルアップロード1人勉強会【PHP】

readouble.com
を参考にした。

まずpublic/storage と storage/app/public 間においてシンボリックリンクを貼るために以下のArtisanコマンドを実行しておく。

php artisan storage:link

以下のような画像をアップロードするbladeを用意する。

<form action="/upload" enctype="multipart/form-data" method="post">
    @csrf
    <input type="file" name="img">
    <input type="submit" value="アップロードする">
</form>

ルーティング

Route::post('upload','ImgTestController@store');

①putを使うパターン

https://laravel.com/api/6.x/Illuminate/Support/Facades/Storage.html#method_put

ソースは色々省略しています。アップロードしたファイルはstorage/app/public/imgに保存される。

use Illuminate\Support\Facades\Storage;

$contents = $request->file('img');
Storage::put('public/img', $contents);

②putFileを使うパターン

https://laravel.com/api/6.x/Illuminate/Support/Facades/Storage.html#method_putFile

ソースは色々省略しています。アップロードしたファイルはstorage/app/public/imgに保存される。

use Illuminate\Support\Facades\Storage;

$contents = $request->file('img');
Storage::putFile('public/img',$contents);

③putFileAsを使うパターン

https://laravel.com/api/6.x/Illuminate/Support/Facades/Storage.html#method_putFileAs

ソースは色々省略しています。アップロードしたファイルはstorage/app/public/imgに画像の元の名前で保存される。
getClientOriginalName()を使い、名前を取得。
ちなみにファイルの拡張子について知りたい場合は、extension()メソッドを使う。

use Illuminate\Support\Facades\Storage;

$contents = $request->file('img');
Storage::putFileAs('public/img', $contents, $contents->getClientOriginalName());


上の①〜③ではStorageファサードを使った。以下ではIlluminate\Http\UploadedFileのメソッドを使用する。

④storeを使うパターン

https://laravel.com/api/6.x/Illuminate/Http/UploadedFile.html#method_store

アップロードしたファイルはstorage/app/public/imgに保存される。

$request->file('img')->store('public/img');

⑤storeAsを使うパターン

https://laravel.com/api/6.x/Illuminate/Http/UploadedFile.html#method_storeAs

アップロードしたファイルはstorage/app/public/imgに画像の元の名前で保存される。

$request->file('img')->storeAs('public/img',$request->file('img')->getClientOriginalName());