お知らせ

現在サイトのリニューアル作業中のため、全体的にページの表示が乱れています。
投稿日:
言語::Perl

PerlのクラスはPythonみを感じる奥ゆかしい作りになっている。

ファクトリ関数でオブジェクトを生成して、そのオブジェクトの参照を引き回すことで実現してる感じ。

確認環境

Env Ver
Perl 5.34.0

基本

ファイル拡張子は.pmが一般的だと思われる。ファイル名とクラス名がリンクしているためPath/To/UpperCamel.pmが多分いい

ハッシュに突っ込んでいく方式

# これがクラス名になる。PATH::TO::NAME構文で書く
package Example;

use strict;

# コンストラクタ
# 第一引数にはpackageのフルパスが入り、第二引数以降に通常引数が入る
sub new {
  # 祝福することでインスタンスオブジェクトを生成できる
  my $self = bless({}, shift);
  # フィールドに値を突っ込む。これがプロパティになる。アクセサはない
  $self->{hoge} = shift;
  $self->{piyo} = shift;

  # インスタンスを返す
  return $self;
}

# メソッド
# 第一引数にはインスタンスオブジェクトが入る。アクセサはない
sub get_hoge {
  my $self = shift;
  return $self->{hoge};
}

1;

オブジェクト生成時にアサインする方式

package Example;

use strict;

sub new {
  my ($class, @args) = @_;
  my $self = bless({
    hoge => shift @args,
    piyo => shift @args
  }, $class);

  return $self;
}

sub get_hoge {
  my $self = shift;
  return $self->{hoge};
}

1;

クラスインスタンスの生成

ディレクトリ構成

├─library
│  └─Example.pm
└─main.pl

コード

クラスのコードは前述の内容

use strict;
# useするモジュールのパスを指定するおまじない
# パスにはクラスファイルを格納している根本のパスを指定する
# 標準モジュールは区別されるようで、この行の下にuse IPC::Open2;とか書いても、ちゃんと本来のモジュールが読み込まれる
use lib './library';

use Example;

# コンストラクタ呼び出しは()で囲まないとエラーになる
my $ex = Example->new("aaaa", 123);
print $ex->{hoge}."\n";
print $ex->{piyo}."\n";
print $ex->get_hoge."\n";

継承

基底クラス

package Example;

use strict;

sub new {
  my $self = bless({}, shift);
  $self->{hoge} = shift;
  $self->{piyo} = shift;

  return $self;
}

sub get_hoge {
  my $self = shift;
  return $self->{hoge};
}

1;

子クラス

package ExampleChild;

use strict;
use parent 'Example';

sub new {
  my ($class, @args) = @_;

  my $self = $class->SUPER::new(shift @args, shift @args);
  $self->{fuga} = shift @args;
  return $self;
}

1;

あとがき

仮引数を名前付きで取るかshiftで取るかは微妙に悩む。shiftの方がPerlらしい気もするし、変数名を変更するときの手間を考えると楽ではある。しかしshiftは読みづらいし、名前付き変数を列挙したほうが読みやすい気もする。

今回Perlをある程度書いてみた感じ、Perlはかなり書き方が自由に感じたし、そこはかなり楽しかったが、コーディング規約という面で見ると自由すぎると困る部分もあるし、判り易いほうがいいのでshiftは避けたほうがよさそうだ。

しかし普段JSを書いたりしている中で、クラスとは新しいオブジェクトを返す関数なのでは?と思っていたので、その辺りの解像度が上がったのは良かった。

参考

ぶっちゃけまともにPerlを書くのはこれが初めてなので、クラスとは直接関係ないサイトもある。

perldoc.jpは公式ドキュメントの日本語訳らしい。今も更新されている。

投稿日:
言語::Perl

base64みたいなコマンドを使いたい時に

確認環境

Env Ver
Perl 5.34 .0

サンプルコード

バッククォートでコマンドを括り、後はシェルスクリプト度同じ理論で書くと行ける

# base64エンコードされた文字列
my $input = 'Kipib2xkKioKKml0YWxpYyoKfn5zdHJpa2V+fgotIGxpCiAgLSBzdAp8VEF8QkxFfAp8LS18LS0tfAp8YWF8YmJifAp8Y2N8ZGRkfAo=';
# 変数を標準出力に展開し、base64の標準入力として使い、base64コマンドの標準出力を戻り値の変数に格納している
my $output = `echo $input | base64 -d`;

print $output;

出力

**bold**
*italic*
~~strike~~
- li
  - st
|TA|BLE|
|--|---|
|aa|bbb|
|cc|ddd|

ダメだったコード

その1

# 標準ライブラリからopen2を取得
use IPC::Open2 qw/open2/;

my $pid = open2 *READ, *WRITE, 'base64 -d';
# base64エンコードされた文字列
print WRITE 'Kipib2xkKioKKml0YWxpYyoKfn5zdHJpa2V+fgotIGxpCiAgLSBzdAp8VEF8QkxFfAp8LS18LS0tfAp8YWF8YmJifAp8Y2N8ZGRkfAo=';
close WRITE;
my $output = <READ>;
close $READ;
waitpid $pid, 0;
print $output;

出力

何故か一行目しか出ない

**bold**

その2

# 標準ライブラリからopen2を取得
use IPC::Open2 qw/open2/;

my $pid = open2 my $reader, my $writer, 'base64 -d';
# base64エンコードされた文字列
print $writer 'Kipib2xkKioKKml0YWxpYyoKfn5zdHJpa2V+fgotIGxpCiAgLSBzdAp8VEF8QkxFfAp8LS18LS0tfAp8YWF8YmJifAp8Y2N8ZGRkfAo=';
close $writer;
my $output = <$reader>;
close $reader;
waitpid $pid, 0;
print $output;

出力

何故か一行目しか出ない

**bold**

参考

投稿日:
言語::JavaScript言語::HTML

adiaryの改造でやったのでメモがてら

環境

Env Ver
Microsoft Edge 120.0.2210.144
PHP 8.2.10

サンプルコード

FORM形式での送信

Web標準でサポートされているため実装が非常に容易。特に理由がなければこれでいい

HTMLコード

<textarea onpaste="handlePasteForm(event)"></textarea>

JSコード

fetch()のヘッダ指定がないが、 fetch()を使う場合はFormDataを使うと勝手に生えるので気にしなくていい。他にもXMLHttpRequest.send()Navigator.sendBeacon()でも生えるらしい。

今回試したEdgeではバウンダリー文字列もちゃんと生えていた。

/** @param {ClipboardEvent} e */
const handlePasteForm = (e) => {
  if (e.clipboardData.files.length) {
    const pasteFile = e.clipboardData.files[0];
    const form = new FormData();
    form.append('image', pasteFile);

    fetch('./test.php', {
      method: 'POST',
      body: form
    });
  } else {
    // 何もしない
  }
};

PHPコード

Content-Typemultipart/form-dataの時だけ中身が入ってくる
参考:https://www.php.net/manual/ja/reserved.variables.files.php

<?php
$file_name = $_FILES['image']['name'];
move_uploaded_file($_FILES['image']['tmp_name'], './'. $file_name);
>

$_FILESの中身はvar_dumpした限りこんな感じだった

array(1) {
  ["image"]=>
  array(6) {
    ["name"]=>
    string(9) "image.png"
    ["full_path"]=>
    string(9) "image.png"
    ["type"]=>
    string(9) "image/png"
    ["tmp_name"]=>
    string(29) "C:\env\msys64\tmp\php3B90.tmp"
    ["error"]=>
    int(0)
    ["size"]=>
    int(3988)
  }
}

JSON形式での送信

Web標準ではないので、これといったやり方もなく、正直面倒だが、どうしてもJSONで送らないといけないときに。

HTMLコード

<textarea onpaste="handlePasteJson(event)"></textarea>

JSコード

HTTPではバイナリを送ることができないため、Base64にエンコードして送る。Base64エンコードに含まれる情報はファイルバイナリのみ。もしFORM送信のようにファイル名も送りたければ、別途送ってやる必要がある。

/** @param {File} file */
const encodeBase64 = (file) => {
  return new Promise ((resolve, reject) => {
    const fr = new FileReader();

    fr.onload = () => {
      const data = fr.result.split(',')[1];
      resolve(fr.result.split(',')[1]);
    };

    fr.onerror = (ev) => {
      reject(ev);
    };

    fr.readAsDataURL(file);
  });
};

/** @param {ClipboardEvent} e */
const handlePasteJson = async (e) => {
  if (e.clipboardData.files.length) {
    const pasteFile = e.clipboardData.files[0];

    const base64File = await encodeBase64(pasteFile);

    fetch('./test.php', {
      method: 'POST',
      headers: {
        'content-type': 'application/json'
      },
      body: JSON.stringify({ image: base64File })
    });
  } else {
    // 何もしない
  }
};

PHPコード

JSONリクエストを取るときはfile_get_contents('php://input')を利用する。$_POSTContent-Typeapplication/x-www-form-urlencodedないしmultipart/form-data時しか取れないからだ。つまりFORMリクエスト以外はfile_get_contents('php://input')で取ると覚えておけばよいだろう。
参考:https://www.php.net/manual/ja/wrappers.php.php#wrappers.php.input

以下のコードはかなりいい加減なので余りアテにしてはいけない(特に拡張子取得の下りがガバガバすぎる)が、取り合えずpngかjpegかgifが送られてきた場合は動いてくれる筈だ。

<?php
// 連想配列にするため、json_decode()の第二引数をtrueにする(指定しないとstdClassになる
$json = json_decode(file_get_contents('php://input'), true);
// Base64エンコードを解除する
$file = base64_decode($json['image']);

// 一時ファイルに落とす
file_put_contents('json_upload', $file);
// 拡張子判別
$mime = mime_content_type('json_upload');
// 拡張子取得
$ext = explode('/', $mime);

// 拡張子付きにリネーム
rename('json_upload', 'image.'. $ext[1]);

参考

JSONでファイルを投げる方式での実装状況については以下の記事が参考になる。

WebAPI でファイルをアップロードする方法アレコレ #API - Qiita

一気通貫で動くサンプルコード

今までの実装が全部動かせるサンプル

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  if(preg_match('/^multipart\/form-data;/', $_SERVER['CONTENT_TYPE'])) {
    // form
    $file_name = $_FILES['image']['name'];
    move_uploaded_file($_FILES['image']['tmp_name'], './'. $file_name);
  } else {
    // json
    $json = json_decode(file_get_contents('php://input'), true);
    $file = base64_decode($json['image']);

    file_put_contents('json_upload', $file);
    $mime = mime_content_type('json_upload');
    $ext = explode('/', $mime);

    rename('json_upload', 'image.'. $ext[1]);
  }
} else {
?>

<!DOCTYPE html>
<html>
<head>
  <meta charset='utf-8'>
  <meta http-equiv='X-UA-Compatible' content='IE=edge'>
  <title>Upload test</title>
  <meta name='viewport' content='width=device-width, initial-scale=1'>
  <script>
    /** @param {File} file */
    const encodeBase64 = (file) => {
      return new Promise ((resolve, reject) => {
        const fr = new FileReader();

        fr.onload = () => {
          const data = fr.result.split(',')[1];
          resolve(fr.result.split(',')[1]);
        };

        fr.onerror = (ev) => {
          reject(ev);
        };

        fr.readAsDataURL(file);
      });
    };

    /** @param {ClipboardEvent} e */
    const handlePasteForm = (e) => {
      if (e.clipboardData.files.length) {
        const pasteFile = e.clipboardData.files[0];
        const form = new FormData();
        form.append('image', pasteFile);

        fetch('./test.php', {
          method: 'POST',
          body: form
        });
      } else {
        // 何もしない
      }
    };

    /** @param {ClipboardEvent} e */
    const handlePasteJson = async (e) => {
      if (e.clipboardData.files.length) {
        const pasteFile = e.clipboardData.files[0];

        const base64File = await encodeBase64(pasteFile);

        fetch('./test.php', {
          method: 'POST',
          headers: {
            'content-type': 'application/json'
          },
          body: JSON.stringify({ image: base64File })
        });
      } else {
        // 何もしない
      }
    };
  </script>
</head>
<body>
  <div>Form:<textarea onpaste="handlePasteForm(event)"></textarea></div>
  <div>Json:<textarea onpaste="handlePasteJson(event)"></textarea></div>
</body>
</html>

<?php
}
?>

EdgeとChromeは同じだったがNode.jsでは異なる挙動をしていたのでそのメモ

確認環境

Env ver
Microsoft Edge 120.0.2210.9
Google Chrome 120.0.6099.130
Node.js 20.0.0

環境別の確認結果

Edge

DevToolsで確認

const err = new Error('test');
Object.getPrototypeOf(err);

// {name: 'Error', message: '', constructor: ƒ, toString: ƒ}

Google Chrome

DevToolsで確認

const err = new Error('test');
Object.getPrototypeOf(err);

// {name: 'Error', message: '', constructor: ƒ, toString: ƒ}

Node.js

node -iで確認

const err = new Error('test');
Object.getPrototypeOf(err);

// {}