2015年5月30日土曜日

PHPでzipファイルを解凍する

phpでzipファイルを解凍したいときにZipArchiveクラスを使うと簡単にできることを知ったので方法をメモ!!


$zip = new ZipArchive();
$res = $zip->open('file.zip');

if ($res) {
    $zip->extractTo('unzip_dir/');
}
$zip->close();


phpinfo()でzipの項目がない場合は、zip拡張を読み込むことで使えるようになります。

2015年3月1日日曜日

Laravel4.2で独自ヘルパーを作成してautoloadする

たまにだけしかできていないですが、laravelの勉強をしていて独自ヘルパーを読み込めるようにしたいと思い、調べてみました。

どんなときにヘルパーを呼び出したかったかというと、セレクトメニューなどで表示する項目は文字だけど、DBに登録するのが一意の数字のときに、ビューで表示したい項目の番号をヘルパーに渡して、表示したい文字列に変換したいときなどです。

まず、ヘルパー用のhelpersディレクトリをappディレクトリ配下に作り、その配下にファイルを作成します。

$ mkdir app/helpers
$ vim app/helpers/HelperDemo.php
<?php
function demo()
{
    return "OK";
}

今回は呼び出せるかを確認したいので、OKを返すようにしておきます。ファイルを作成できたら、オートロードするようにcomposer.jsonを変更します。

$ vim composer.json
"autoload": {
      "classmap": [
           "app/commands",
           "app/controllers",
           "app/models",
           "app/database/migrations",
           "app/database/seeds",
           "app/tests/TestCase.php"
      ],
        "files": [
            "app/helpers/HelperDemo.php"
        ]
}

記述を追加するのは、autoloadの中にfilesという名前で要素を作成し、読み込みたいファイルまでのパスを記述します。

composer.jsonの変更が完了したら、dump-autoloadして読み込めばdemo()メソッドは呼び出すことができるようになります。

ヘルパーを複数作る可能性があるので、クラスのようにディレクトリを指定して、ディレクトリ内のファイルを全部読み込むことができれば良いなと思いました。

知らないだけでできるのかもしれませんが…

2015年2月17日火曜日

EC2のインスタンスにsshで接続できなくなった

先日、AWSのEC2でたてた、t2.microインスタンスでnginxとphp-fpm、MySQL、memcacheを動かすWEBサーバの設定をしてみました!

金曜日に設定し、土日は放置して月曜日に接続しようとしたところ、突然SSH接続ができなくなりました…。

環境構築がとても苦手なので、このまま接続できなかったらどうしようと不安が募ります。

不安になっていても、サーバは受け入れてくれないのでできることはしようと、表示された下記のエラーを調べてみることにしました。

$ ssh -i .ssh/ssh.pem ec2-user@***.***.***.***
shell request failed on channel 0

エラーの内容について、調べてみるとセッション数が多いのではないか?などの情報があったのですが、接続できるのは自分だけだったため、その可能性は低いなと感じました。

その後も調べていたのですが、あまり良い情報がなかったため(恐らく検索方法が悪かった)、ダメ元で再トライ!

 すると…

$ ssh -i .ssh/ssh.pem ec2-user@***.***.***.***
Last login: Mon Feb  9 11:12:21 2015 from ***.ap.plala.or.jp

       __|   __|_   )
       _|    (      /    Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2014.09-release-notes/
/bin/bash: Cannot allocate memory
Connection to ***.***.***.*** closed.

今度は、一瞬接続できたのですが、すぐに切断されてしまいました。。

まったく原因がわかっていなかったので、とても焦っていたのですが、一瞬接続できたときになにやらエラーらしきものが表示されていました!

/bin/bash: Cannot allocate memory

エラーからもわかるように、メモリ不足で接続ができないということだったようです‼︎

原因がわかり、sshではどうしようもないと思ったので、AWSのコンソールからインスタンスを再起動したところ、無事に接続できるようになりました!

再起動後になにが重かったのか調べてみたところ、MySQLが重かったようです。。

ということで、今回はt2.microだとLEMP + memcacheはちょっとパンパンということがわかりました!

2015年2月1日日曜日

スマホサイトのファイルアップロードボタンをCSSでデザイン変更

仕事でスマホサイトのアップロードボタンを作成したときに、少し躓いたことがあったのでまとめてみようと思います。

そもそもデフォルトのデザインから変更できるのか?という疑問があったのですが、いろいろなサイトで変更されていることもあるので、とりあえず調べてみました。

ざっくり調べて、ざっくり理解したところによると… 

ボタンとinputタグを別に記述して、javascriptでイベントを実行する!

これで出来るだろうと思ったので、ボタン部分をpタグで作成してinputタグをcssのdisplay: noneで非表示に変えて、jQueryでイベントを発生させる方法で進めることにしました。

そして、最初にできたコードが下記になります。

■HTML
<input type="file" id="decorate">
<div class="upload">
    <p>UPLOAD</p>
</div>

■JS
$(document).ready(function(){
    $('.upload').on('click', function(){
        $('#decorate').trigger('click');
    });
});

■CSS
#decorate {
    display: none;
}

.upload {
    border: solid 1px #a3ffa3;
    background-color: #b7ffb7;
    width: 200px;
    text-align: center;
    border-radius: 10px;
}


inputタグには、idをつけておきCSSでdisplay: noneすることで、inputタグはあるけど表示していない状態にしています。

JSでは、classにuploadが指定されているdivタグがクリックされたときに、非表示にしてあるinputタグに対して、clickイベントを発生させるようにしてあります。

この部分が完成して、iPhoneで確認したところ無事に想定通りの動作をしてくれました!

iPhoneでの確認ができて、意外と簡単にできたなーと思いながら、Androidで確認してみると…

事件が発生しました。

何度、ボタンを押してもなにも起こらない。。

うんとも、すんともいわない。

ガーンッと意気消沈しながらも、なにが問題なのかを確認するためにdisplay: noneをコメントアウトして確認してみると、ファイルを選択できることが発覚!

ということは、display: noneだとAndroidはダメなのか。。詰んだな。

一瞬、うん、諦めようという思いが湧いたような湧かなかったような…と、どうでも良い間がありつつ再度、調べてみると初見のvisibilityというプロパティを発見!

ざっくり説明するとdisplay: noneは、要素自体がなくなってしまうのですが、visibility: hiddenの場合は要素を残したまま非表示にすることができるというものです。

ということで、早速、display: noneからvisibility: hiddenに変更

■CSS
#decorate {
    visibility: hidden;
}

頼む!と思いを込めて、ボタンをタップすると…

無事にファイルを選択できました!

ほっと安心するも、非表示にしただけなのでデフォルトのサイズ分要素が存在していて、その部分が空白になってしいました。。

新たな問題発生。ですが、これはサイズを0にすれば大丈夫だろうと思ったので、cssでheightとwidthを0pxに変更

■CSS
#decorate {
    visibility: hidden;
    height: 0px;
    width: 0px;
}


もう一度、確認してみるとAndroidは想定通りだったのですが、iPhoneは変わりませんでした。。

ChromeのWEBインスペクタで確認してみると、各プロパティは適用されていることになっているのですが、見栄えはNG。

0pxがダメなら、1pxはどうなんだろうと思い1pxにしてみると今度は想定通りに変更することができました。

visibilityで非表示にしているので、1px存在してもボタンと重なる位置であれば、大丈夫なので、1pxを設定することにしました。

ということで、完成した最終版は下記のような感じになります。(CodePenを使ってみたかったので使ってみました)

See the Pen pvPKEP by masahiro (@masahiiiro) on CodePen.


AndroidとiPhoneでちょいちょいズレが生じるので、両方で問題がないかを確認するのは大変だなと、改めて思いました。

2014年12月4日木曜日

MBAのVagrantでubuntuを起動する

Windows7にVirtual Boxをインストールしたので、vagrantもインストールしたのですが、低スペックだったせいかVMを起動できませんでした。。

その後、MacBookAirを購入したので、Virtual Boxとvagrantをインストールし、リベンジしてみました。

ということで、vagrantのbox追加からdestroyまでをまとめていこうと思います!

まずは、boxの追加をします。boxは、vagrant cloudで好きなものや用途にあったものを選びます。

今回は、ubuntu 14.0.4を選んでみました。
boxを選んだら、ターミナルでboxを追加します。

boxの追加はvagrantのboxコマンドで行います。
まずは追加なので、サブコマンドにaddを指定し、引数にbox名とボックスのURLを入力します。

vagrant box add box名 ダウンロード元URL

上記の形式に沿って、実行したのが下記のコマンドになります!

$ vagrant box add ubuntu/trusty32 https://vagrantcloud.com/ubuntu/boxes/trusty32
==> box: Loading metadata for box 'https://vagrantcloud.com/ubuntu/boxes/trusty32'
==> box: Adding box 'ubuntu/trusty32' (v14.04) for provider: virtualbox
    box: Downloading: https://vagrantcloud.com/ubuntu/boxes/trusty32/versions/14.04/providers/virtualbox.box 
==> box: Box download is resuming from prior download progress 
==> box: Successfully added box 'ubuntu/trusty32' (v14.04) for 'virtualbox'! 

これでboxを追加できました!追加したboxは、listサブコマンドで確認できます。

$ vagrant box list 
ubuntu/trusty32 (virtualbox, 14.04) 

追加したときに指定した、box名があれば追加完了です!追加できていることを、確認できましたので、実際に起動してみようと思います。

VMを起動するときは、vagrant upコマンドを実行します。

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider... 
==> default: Importing base box 'ubuntu/trusty32'... 
==> default: Matching MAC address for NAT networking... 
==> default: Checking if box 'ubuntu/trusty32' is up to date... 
==> default: Setting the name of the VM: virtual-env_default_1417616732058_47717 
==> default: Clearing any previously set forwarded ports... 
==> default: Clearing any previously set network interfaces... 
==> default: Preparing network interfaces based on configuration... 
    default: Adapter 1: nat 
==> default: Forwarding ports... default: 22 => 2222 (adapter 1) 
==> default: Booting VM... 
==> default: Waiting for machine to boot. This may take a few minutes... 
    default: SSH address: 127.0.0.1:2222 
    default: SSH username: vagrant 
    default: SSH auth method: private key 
    default: Warning: Connection timeout. Retrying... 
    default: Warning: Remote connection disconnect. Retrying... 
==> default: Machine booted and ready! 
==> default: Checking for guest additions in VM... 
==> default: Mounting shared folders... 
    default: /vagrant => /Users/Development/virtual-env

起動時に設定情報が表示され、エラーもなくコマンドが終われば起動完了です。

問題なく起動できたら、vagrantのsshコマンドで起動したVMに接続してみます。

$ vagrant ssh 
Welcome to Ubuntu 14.04.1 LTS (GNU/Linux 3.13.0-40-generic i686)
   * Documentation: https://help.ubuntu.com/

   System information as of Wed Dec 3 14:25:52 UTC 2014 

   System load: 0.83 Processes: 89 
   Usage of /:  2.6% of 39.34GB Users logged in: 0
   Memory usage: 10% IP address for eth0: 10.0.2.15 
   Swap usage:    0% 

   Graph this data and manage this system at: 
       https://landscape.canonical.com/ 

   Get cloud support with Ubuntu Advantage Cloud Guest:       
       http://www.ubuntu.com/business/services/cloud

0 packages can be updated. 
0 updates are security updates. 

vagrant@vagrant-ubuntu-trusty-32:~$ 

接続できるかドキドキしましたが、無事に接続できることが確認できました!

作業が終わり切断したいときは、exitコマンドを使います。

vagrant@vagrant-ubuntu-trusty-32:~$ exit 
logout
Connection to 127.0.0.1 closed. 

切断後、VMを停止したいときは、haltコマンドで停止することができます。

$ vagrant halt 
==> default: Attempting graceful shutdown of VM... 

追加したVMがいらなくなったときは、destroyコマンドで破棄できます。

$ vagrant destroy 
   default: Are you sure you want to destroy the 'default' VM? [y/N] y
==> default: Destroying VM and associated drives...

作業自体は、難しくないのでどうなっても良い環境が欲しいときは良いと思います。

2014年12月3日水曜日

Symfony1.4のDoctrineで大量のデータ操作をした時に Allowed memory size...のエラーになる

Symfony1.4のDoctrineで、1万件くらいのデータを取り出してforeachで1件ずつ更新しようとしたところ、Allowed memory size..のエラーがでてしまいました。

PHP Fatal error:  Allowed memory size of 536870912 bytes exhausted (tried to allocate 32 bytes)

このエラーが出た時は、php.iniのmemory_limitを変更すれば、防げるようになるのですが、php.iniを変更できなかったり、変更したくないことも多いと思います。。

今回は、php.iniを変更したくなかったので、設定以外で対応したいと思っていたところ、Doctrineにはfree()というものがあり、オブジェクトを解放できることがわかりました。

free()は、Doctrine_RecordとDoctrine_Query、Doctrine_Collectionで使うことができるみたいです。

試しにテーブルから5000件抽出し、ループでupdated_atカラムを更新するプログラムを実行してみました。

// Usersテーブルから5000件のデータを抽出
$q = Doctrine_Query::create()
     ->from('User u')
     ->limit(5000)
     ->orderBy('u.id ASC')
     ->execute();

foreach ($q as $k => $val) {
      $val->updated_at = date("Y-m-d H:i:s");
      $val->save();
      if ($k % 500 == 0){
            printf("%d : %dKB\n", $k, (memory_get_usage(true) / 1024));
      }
      $val->free();
}
$q->free();
printf("End : %dKB\n", $k, (memory_get_usage(true) / 1024));


効果があるのかを判定するために、まずは$val->free()をコメントアウトして実行してみました。

0        : 94976KB
500   : 99072KB
1000 : 102912KB
1500 : 106752KB
2000 : 111616KB
2500 : 115712KB
3000 : 119552KB
3500 : 123392KB
4000 : 127232KB
4500 : 131840KB
End    : 137728KB

実行結果からもわかるように、メモリがどんどん増えていきます。今回はこれを増えないようにしたいので、$val->free()を有効にして実行してみます。

0       : 94976KB
500   : 95488KB
1000 : 95744KB
1500 : 96000KB
2000 : 97024KB
2500 : 97024KB
3000 : 97024KB
3500 : 97024KB
4000 : 97024KB
4500 : 97536KB
End    : 99584KB

多少は増えますが、なにもしないときよりも38144KBのメモリを抑えることができましたので、大量にデータを扱うときは対策をするべきだなと思いました。

また、テーブルを結合している場合は、引数にtrueを渡してfree(true)と指定することで、リレーションのオブジェクトもすべて解放することができます。

2014年11月24日月曜日

Laravelでマイグレーション後にモデル作成してもデータが登録ができない

いままで、SQLiteを使ったことがなかったため、ちょっと試してみようということで、勉強を始めたlaravelで触ってみることにしました。

ですが、開始早々に躓いてしまいました。。

どんなことに躓いたかといいますと、マイグレーションからテーブルを作成し、テストデータを登録しようとしたら、追加できなかったのです。

ヘタレ具合を露呈させてしまいますが、まとめてみました。

まず、artisanコマンドでマイグレーションファイルを生成します。

$ php artisan migrate:make profile
Created Migration: 2014_11_23_230533_profile
Generating optimized class loader

上記のコマンドを実行すると「2014_11_23_230533_profile.php」というファイルがapp/databases/migrations配下に生成されます!

この生成したファイルにテーブルのカラム情報を記述していきます。記述するのは、マイグレーションファイルのupメソッドです。

$ vim app/database/migrations/2014_11_23_230533_profile.php
public function up()
{
// profileテーブル作成
       Schema::create('profile', function($table){
           $table->increments('id');
           $table->string('name');
           $table->timestamps();
       });
}

Schemeのcreateでテーブル名とカラム情報を設定します。最後のtimestamp()ではcreated_at、updated_atというカラムを作成できます!

記述が終わったら、artisanでmigrateします。

$ php artisan migrate

これでテーブルが作成できたので、モデルを作っていきます。

モデルでは、下記のようにテーブルや変更しないカラム、変更する可能性のあるカラムを指定しておきます。

 $ vim /app/models/Profile.php
 class Profile extends Eloquent {
     protected $table    = 'profile';
     protected $gurded   = array('id');
     protected $fillable = array('name');
 }

モデルを作成したら、今度はコントローラーでデータを追加してみます。

$ vim /app/controllers/ProfileController.php
public function index()
{
    $profile = new Profile;

    $profile->name = 'jack';
    $profile->save();
}

new Profileでインスタンスを生成して、save()で保存するように記述して、完了です。

これで、今回のテストデータ登録ができるはずだったのですが、エラーが出てしまいました。

Symfony\Component\Debug\Exception\FatalErrorException thrown with message "Call to undefined method Profile::save()"

Stacktrace:
#1 Symfony\Component\Debug\Exception\FatalErrorException in /app/controllers/ProfileController.php:15
#0 Illuminate\Exception\Handler:handleShutdown in <#unknown>:0

エラーを確認すると、saveメソッドが定義されていないというものでした。

save()が定義されていないということは、モデルを作ったけど、読み込まれていないのか!?と思い、dump-autoloadしてみることにしました。

$ composer dump-autoload
Generating autoload files

無事にコマンドが成功したので、再度登録を試みるも変わらずに同じエラーが出てしまいました。。

まだ、読み込めていないのかなんなのかよくわらないので、$profileをvar_dumpしてみました。var_dumpしていろいろ表示されてもよく分からないけど、とりあえずvar_dump‼︎

$profile = new ProfileTable;
var_dump($profile);

こんな感じでProfileの内容をvar_dumpしたところ•••
 
object(Profile)#131 (1) { ["connection":protected]=> NULL }

あ•••
NULLという文字が見える。。
これは、モデルがちゃんと生成できていないということなのか!?

なかなか混乱してきましたが、モデルがちゃんと読み込まれていない可能性があるということは、dump-autoloadでモデルがちゃんと入らなかったのか?ということで、dump-autoloadで更新されるautoload_classmap.phpを確認してみました。

ファイル内でprofileを検索したところ、モデルは読み込まれておらず、代わりにマイグレーションがProfileとして読み込まれていました。。

$ vim vendor/composer/autoload_classmap.php
'Profile' => $baseDir . '/app/database/migrations/2014_11_23_230533_Profile.php',

これは、マイグレーションとモデルが同じ名前で、すでにマイグレーションがあるからモデルが追加されていない?ということなのか?

もしそういうことなら、モデルの名前を変えればエラーじゃなくなるのか?

うーん、よく分からない。
考えても分からないので、とりあえずモデル名を変えてみます。。

$ vim /app/models/Profiles.php
<?php
class Profiles extends Eloquent {
    protected $table    = 'profile';
    protected $gurded   = array('id');
    protected $fillable = array('name');
}

モデルのもともとProfileだったものをProfilesに変更してみました。コントローラーで呼び出しているところも変更します。

$ vim /app/controllers/ProfileController.php
$profile s = new Profiles;

$profiles->name = 'jack';
$profiles->save();

ファイルの変更が終わったら、dump-autoloadで更新!

更新後にautoload_classmap.phpを確認したところ、Profilesが追加されているのが確認できました。

$ vim vendor/composer/autoload_classmap.php
'Profile' => $baseDir . '/app/database/migrations/2014_11_23_230533_Profile.php',
'Profiles' => $baseDir . '/app/models/Profiles.php',

モデルが読み込まれたので、実行してみるとエラーは解消され、無事に登録することができました。

+--+-------+----------------------------+----------------------------+
|id  |name  |created_at                     |updated_at                   |
+--+-------+----------------------------+----------------------------+
| 1  |jack    |2014-11-24 00:42:36|2014-11-24 00:42:36|
+--+-------+----------------------------+----------------------------+

今回の原因は、先に作成したマイグレーションがProfileとして読み込まれていたため、同じ名前で指定したProfileモデルが追加されなかったということみたいです。


macOSでminikubeをインストールしようとしたら書き込みエラーになった

ローカル環境でKubernetesを使えるようにしようと環境構築中にエラーが 下記が今回のエラーで書き込みできない感じのメッセージが出ています。  $ brew install minikube Updating Homebrew... Error: The following ...