Scala コラッツの問題

友人へ

コラッツの問題 - Wikipedia

が面白かったのでScalaで書いてみよう!という記事。

Scalaをインストールしていなくても、Macユーザーの場合

$ brew install scala

で一発でインストールできるので、実行してみよう!

コラッツの問題

コラッツの問題は、「任意の正の整数 n をとり、

  • n が偶数の場合、n を 2 で割る
  • n が奇数の場合、n に 3 をかけて 1 を足す

いう操作を繰り返すと、どうなるか」というものである。 「どんな初期値から始めても、有限回の操作のうちに必ず 1 に到達する(そして 1→4→2→1 というループに入る)」という主張が、コラッツの予想である。

例として、初期値を 6 にすると、 6, 3, 10, 5, 16, 8, 4, 2, 1という、1に到達する数列を得る。 このような数列をコラッツ数列と呼ぶ。 (Wikipediaより)

コラッツ数列を求める

例えばcollatz(6)と実行したらList(6, 3, 10, 5, 16, 8, 4, 2, 1)と返る関数を作る。

def collatz(num: Int): List[Int] = {
  def proc(list: List[Int]): List[Int] = {
    if (list.head == 1) list
    else if (list.head % 2 == 0) proc((list.head / 2) :: list)
    else proc((list.head * 3 + 1) :: list)
  }
  if (num <= 0) Nil
  else proc(num :: Nil).reverse
}

これを先ほどインストールしたScalaで実行してみよう。

ターミナルで$ scalaと打つとREPLが実行されるので、そこで試してみる。 REPLで:pasteと打つと複数行のコードのペーストが簡単にできる。 ペーストをしたら、ctrl-Dでペーストモードを終了し、collatz(6)のように実行してみよう。

$ scala
Welcome to Scala 2.12.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_172).
Type in expressions for evaluation. Or try :help.

scala> :paste
// Entering paste mode (ctrl-D to finish)

def collatz(num: Int): List[Int] = {
  def proc(list: List[Int]): List[Int] = {
    if (list.head == 1) list
    else if (list.head % 2 == 0) proc((list.head / 2) :: list)
    else proc((list.head * 3 + 1) :: list)
  }
  if (num <= 0) Nil
  else proc(num :: Nil).reverse
}

// Exiting paste mode, now interpreting.

collatz: (num: Int)List[Int]

scala> collatz(6)
res0: List[Int] = List(6, 3, 10, 5, 16, 8, 4, 2, 1)

上記例が正しく求まった。

コラッツ数列をたくさん求める

(1 to 10).map(collatz).foreach(println)

結果は

List(1)
List(2, 1)
List(3, 10, 5, 16, 8, 4, 2, 1)
List(4, 2, 1)
List(5, 16, 8, 4, 2, 1)
List(6, 3, 10, 5, 16, 8, 4, 2, 1)
List(7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1)
List(8, 4, 2, 1)
List(9, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1)
List(10, 5, 16, 8, 4, 2, 1)

(1 to 10)の部分を書き換えて、君だけのコラッツ数列のリストを生成しよう!

とっても長いコラッツ数列を求める

上記例だとcollatz(9)が数列の長さが大きい。 では、初期値が1から100,000の中で一番長さが大きいコラッツ数列を求めてみよう。

中間リストが大きくなっても、ストリームを使えばへっちゃらだ。

(1 to 100000).toStream.map(collatz).map(xs => (xs.head, xs.length)).maxBy(_._2)

結果は

(77031,351)

初期値77031のコラッツ数列の長さは351ということらしいので、確認してみよう。

println(collatz(77031).mkString(", "))
77031, 231094, 115547, 346642, 173321, 519964, 259982, 129991, 389974, 194987, 584962, 292481, 877444, 438722, 219361, 658084, 329042, 164521, 493564, 246782, 123391, 370174, 185087, 555262, 277631, 832894, 416447, 1249342, 624671, 1874014, 937007, 2811022, 1405511, 4216534, 2108267, 6324802, 3162401, 9487204, 4743602, 2371801, 7115404, 3557702, 1778851, 5336554, 2668277, 8004832, 4002416, 2001208, 1000604, 500302, 250151, 750454, 375227, 1125682, 562841, 1688524, 844262, 422131, 1266394, 633197, 1899592, 949796, 474898, 237449, 712348, 356174, 178087, 534262, 267131, 801394, 400697, 1202092, 601046, 300523, 901570, 450785, 1352356, 676178, 338089, 1014268, 507134, 253567, 760702, 380351, 1141054, 570527, 1711582, 855791, 2567374, 1283687, 3851062, 1925531, 5776594, 2888297, 8664892, 4332446, 2166223, 6498670, 3249335, 9748006, 4874003, 14622010, 7311005, 21933016, 10966508, 5483254, 2741627, 8224882, 4112441, 12337324, 6168662, 3084331, 9252994, 4626497, 13879492, 6939746, 3469873, 10409620, 5204810, 2602405, 7807216, 3903608, 1951804, 975902, 487951, 1463854, 731927, 2195782, 1097891, 3293674, 1646837, 4940512, 2470256, 1235128, 617564, 308782, 154391, 463174, 231587, 694762, 347381, 1042144, 521072, 260536, 130268, 65134, 32567, 97702, 48851, 146554, 73277, 219832, 109916, 54958, 27479, 82438, 41219, 123658, 61829, 185488, 92744, 46372, 23186, 11593, 34780, 17390, 8695, 26086, 13043, 39130, 19565, 58696, 29348, 14674, 7337, 22012, 11006, 5503, 16510, 8255, 24766, 12383, 37150, 18575, 55726, 27863, 83590, 41795, 125386, 62693, 188080, 94040, 47020, 23510, 11755, 35266, 17633, 52900, 26450, 13225, 39676, 19838, 9919, 29758, 14879, 44638, 22319, 66958, 33479, 100438, 50219, 150658, 75329, 225988, 112994, 56497, 169492, 84746, 42373, 127120, 63560, 31780, 15890, 7945, 23836, 11918, 5959, 17878, 8939, 26818, 13409, 40228, 20114, 10057, 30172, 15086, 7543, 22630, 11315, 33946, 16973, 50920, 25460, 12730, 6365, 19096, 9548, 4774, 2387, 7162, 3581, 10744, 5372, 2686, 1343, 4030, 2015, 6046, 3023, 9070, 4535, 13606, 6803, 20410, 10205, 30616, 15308, 7654, 3827, 11482, 5741, 17224, 8612, 4306, 2153, 6460, 3230, 1615, 4846, 2423, 7270, 3635, 10906, 5453, 16360, 8180, 4090, 2045, 6136, 3068, 1534, 767, 2302, 1151, 3454, 1727, 5182, 2591, 7774, 3887, 11662, 5831, 17494, 8747, 26242, 13121, 39364, 19682, 9841, 29524, 14762, 7381, 22144, 11072, 5536, 2768, 1384, 692, 346, 173, 520, 260, 130, 65, 196, 98, 49, 148, 74, 37, 112, 56, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1

計算時間を求める

せっかくなので、上記の長さが最大のコラッツ数列を求めるプログラムの計算時間を計測しよう。

scala> :paste
// Entering paste mode (ctrl-D to finish)

def calcTime[A](proc: => A): (A, Long) = {
  val start = System.currentTimeMillis
  (proc, (System.currentTimeMillis - start))
}

// Exiting paste mode, now interpreting.

calcTime: [A](proc: => A)(A, Long)

scala> def longest = (1 to 100000).toStream.map(collatz).map(xs => (xs.head, xs.length)).maxBy(_._2)
longest: (Int, Int)

scala> calcTime(longest)
res11: ((Int, Int), Long) = ((77031,351),586)

586[ms]で実行することができた。

proc: => Aと指定することで、関数を評価せずに渡すことができる。 ジェネリクスを指定したので、どんな関数でもぶちこめる。便利だね。

結論

Scala楽しいよ!

Common Lisp始めました

Common Lisp始めました💕

先生や友人から「憧れの言語はCommon LispC++」という話を聞きました。 どうして身の回りの強い人たちは揃いも揃って同じことを言うのだろうか(N=2)。 というわけで、まずは片方初めてみることにします。

Common Lispはカッコがいっぱいあるよくわからない言語、C++は暗記ゲーという偏見がありました。 どちらも習得には時間がかかりそうだが、知らなかった概念を早く学べるのはどちらだろうと考え、 Common Lispを選択することにした。 f:id:mi12cp:20180930070200j:plain 以下、やったことをだらだらと書いているだけなので、記事としての有用性は期待しないでください。

インストール

Mac環境では

$ brew instal clisp

で一発。 REPLの実行は$ clisp, ファイルの実行は$clisp example.lisp

とりあえずやってみる

まずは他人のコードを見よう見まねでAtCoder Beginner ContestのA, B問題を解いてみる。

ABC102 A - Multiple of 2 and N

入力: 数字一つ、出力: 数字ひとつの簡単な例。 多分最初に書いたのがこれかなあ。他人のコードをほぼ丸写しになったと思う。

; A - Multiple of 2 and N
; https://beta.atcoder.jp/contests/abc102/tasks/abc102_a

(defun solve (n)
  (if (= (mod n 2) 0)
    n
    (* n 2)))

(format t "~A" (solve (read)))

だいたいこれを見れば基本的な構文がイメージがつく。

  • 関数と引数のペアをカッコでくくる
  • 全て前置記法で書かれる。そのため演算子の優先順位など気にしなくてよい(そもそも演算子という概念があるのかわからないが)
  • インデントや改行に規定はないが、おそらく次のような慣習があると思われる
    • (で終わる行は書かない
    • )で始まる行は書かない
    • 同じ関数に対する引数のリストは横に並べても良いし、縦に並べても良い。縦に並べる時はインデントを揃える

ABC110 B - 1 Dimensional World's Tale

早速、別の問題を試してみる。

; B - 1 Dimensional World's Tale
; https://beta.atcoder.jp/contests/abc110/tasks/abc110_b
 
(defun split-and-parse-integer (string)
  (loop for i = 0 then (1+ j)
    as j = (position #\Space string :start i)
    collect (parse-integer (subseq string i j))
    while j))
 
(defun input-format ()
  (let ((n (read)) (m (read)) (x (read)) (y (read)))
    (let ((xs (split-and-parse-integer (read-line)))
          (ys (split-and-parse-integer (read-line))))
      (let ((xx (cons x xs))
            (yy (cons y ys)))
        (list xx yy)))))
 
(defun solve (lis)
  (let ((xx (car lis))
        (yy (car (cdr lis))))
    (let ((biggest-x  (car (sort (copy-list xx) #'>)))
          (smallest-y (car (sort (copy-list yy) #'<))))
      (if (< biggest-x smallest-y)
          "No War"
          "War"))))
 
(format t "~A" (solve (input-format)))

入力例が若干複雑であった。 split-and-parse-integerは入力の一行の文字列をスペース区切りにし、数字のリストにするもの。 loopの書き方が分からなかったので、ネットで適当に拾ったものを使った。

lispletを初めて知った。letの基本的な構文は下記の通り

(let ((a 2)
      (b 3))
  (+ a b))

変数のスコープの指定を関数のように書くのが面白い。 このletの第二引数の(+ a b)の部分がそのまま(let ...)を評価した値として返る。 つまりletは式で、他の言語でいう即時関数のようなものだ。

解答を見返すと若干冗長な部分や雑な部分があった。 input-formatの中のletは一つ減らせるし、 リストの中の最大の要素を求めるのがわからずソートしたり、 cadrのような書き方を知らなかったりしていますね。

読んだ

1~14章の基本的な構文と、14章の関数型の部分を読んだ これを買った日にリスプモンスターが夢に出てきました。

f:id:mi12cp:20180930070200j:plain

おまけ

ABC109 C - Skip

解いた問題だけ貼るのもなんなので、この記事を書いている最中に一問解いてみよう。 と思ったらTLEを出してしまった。 あとで直しまーす。

; C - Skip
; https://abc109.contest.atcoder.jp/tasks/abc109_c

(defun split-and-parse-integer (string)
  (loop for i = 0 then (1+ j)
    as j = (position #\Space string :start i)
    collect (parse-integer (subseq string i j))
    while j))

(defun input-format ()
  (let ((n (read)) (x (read))
        (xs (split-and-parse-integer (read-line))))
    (cons x xs)))

(defun intervals (as)
  (if (cdr as)
    (cons (- (cadr as) (car as)) (intervals (cdr as)))
    nil))

(defun mygcd (a b)
  (if (= b 0) a (gcd b (mod a b))))

(defun solve (xx)
   (let* ((sxs (sort (copy-list xx) #'<))
          (ixs (intervals sxs)))
      (reduce #'mygcd ixs)))

(format t "~A" (solve (input-format)))

結論

Lispガリガリ積み重ねていく感じが楽しいですね。 再帰とか関数型っぽい書き方を少し知って入れば導入はできるなという感じ。

Angular6 コンポーネントやサービスでのデータのやりとり

Angular6でいくつかプロジェクトを作ったので、 コンポーネントやサービスでのデータのやりとりについて、知見をまとめます。

現状の個人的なベターな書き方ですが、オレオレな部分があるかもしれないです。

目次

  1. 親子関係のコンポーネントでのデータのやりとり
  2. サービスを利用したデータのやりとり

Angularのバージョン

  • Angular: 6.1.7
  • rxjs: 6.3.2
  • typescript: 2.7.2

  • 新規プロジェクトの作成

$ ng new angular-data-example

1. 親子関係のコンポーネント

コンポーネントの作成

コンポーネントbooksにて本の一覧を表示し、選択したbookに対して 子コンポーネントbooks-detailで編集を行うことを考える。

BooksDetailComponentBooksComponent でしか利用しないのであれば、 BooksComponent のフォルダ内に含める。

$ ng g component books
$ ng g component books/books-detail

それぞれのコンポーネント<app-books></app-books>, <app-books-detail></app-books-detail> のように利用できる。

Inputの部分

BooksComponentからBooksDetailComponentに情報を渡す。

  • 基本的には[book]="selectedBook"だけでOK
  • オブジェクトや配列を渡す際は参照を渡すことになるので注意が必要
    • 下記の例では、book.copyで自分自身をディープコピーするメソッドを作成した。

(選択部分 books.component.html)

<div *ngFor="let book of books">
  <button type="button" (click)="selectBook(book)">選択</button>
</div>

books.component.ts

selectBook(book: Book): void {
  // .copy() はディープコピーの自作のメソッド
  this.selectedBook = book.copy();
}

books.component.html

<app-books-detail [book]="selectedBook" (edited)="onEdited($event)"></app-books-detail>

books-detail.component.ts

import { Input, Output, EventEmitter } from '@angular/core';
// ...

@Input() book: Book;

Outputの部分

命名に関しては、

などに統一すると良いと思う。

(編集部分 books-detail.component.html)

<div *ngIf="book != null">
  <form>
    <input type="text" name="title" id="title" [(ngModel)]="book.title">
    <input type="text" name="content" id="content" [(ngModel)]="book.content">
    <button type="submit" (click)="edit()">変更</button>
  </form>
</div>

books-detail.component.ts

import { Input, Output, EventEmitter } from '@angular/core';
// ...

@Output() edited = new EventEmitter<Book>();
// ...
edit(): void {
  // .copy() はディープコピーの自作のメソッド
  this.edited.emit(this.book.copy());
}

books.component.html

<app-books-detail [book]="selectedBook" (edited)="onEdited($event)"></app-books-detail>

books.component.ts

onEdited(book: Book): void {
  // ...
}

注意点

簡単な状況ならこの方法で十分だが、複雑なコンポーネントの関係では利用できない。 例えば、孫コンポーネントを利用する程度であっても、上記のことを二回繰り返さなければならず、 見通しの悪いコードになった経験がある。

完全に兄弟関係にあるコンポーネント間のみで利用することが分かっている場合のみ利用するのが良いと思う。

2. サービスを利用したデータのやりとり

特定のデータitemsを複数のコンポーネントで利用することを考える。 コンポーネント間は複雑な関係になっていて、Input, Outputでは難しいとする。

itemsはサービスで一元管理し、変更した内容を都度各コンポーネントに送信・取得できるようにする。

サービスの作成

$ ng g service items

サービスで利用するクラス reactive-data.ts

サービス内部で利用する処理の共通部分を予め自作した。 もっと良い方法もあるかもしれない。

export class ReactiveData<T> {

  private cache: T;
  private subject = new Subject<T>();
  private source = this.subject.asObservable();

  constructor(initialValue: T, subscriptionMethod: () => Observable<T> = null) {
    this.cache = initialValue;

    if (subscriptionMethod != null) {
      subscriptionMethod().subscribe(
        data => this.setData(data)
      );
    }
  }

  public getData(): Observable<T> {
    return this.source.pipe(startWith(this.cache));
  }

  public snapshot(): T {
    return this.cache;
  }

  public setData(data: T): void {
    this.subject.next(data);
    this.cache = data;
  }

}

基本的な例

サービス items.service.ts

// 利用頻度の高いrxjsのオペレーター
// rxjs5以前ではインポートの仕方が異なるので注意
import { Observable, Subject, of, merge, zip } from 'rxjs';
import { map, tap, filter, first, flatMap, startWith } from 'rxjs/operators';

import { ReactiveData } from './reactive-data';
// ...

// 引数は共有したいデータの初期値
private items: ReactiveData<Items> = new ReactiveData<Items>(null);
// ...
public getItemsReactiveData() {
  return this.items;
}

サービスを利用するコンポーネント some-component.ts

ItemsServiceの使い方の例。 作成したいコンポーネントがいくつかあっても、同様に記述すれば良い。

import { ItemsService } from '../items.service';
import { ReactiveData } from '../reactive-data.service';
// ...

items: Items;
itemsReactiveData: ReactiveData<Items>;

constructor(
  private modalsService: ModalsService,
) {
  this.itemsReactiveData = this.itemsService.getItemsReactiveData();
}

ngOnInit() {
  // サービスのitemsの初期値(または呼び出し前に変更があった場合、最後の値)を取得し、
  // その後サービスのitemsの変更があればそれを受け取る
  this.itemsReactiveData.getData().subscribe(
    items => {
      this.items = items;
    }
  );
}

someMethod(items: Items): void {
  // サービスのitemsを更新したい時に使うメソッド
  this.itemsReactiveData.setData(items);
}

otherMethod(): Items {
  // 単にサービスのitemsの現在の値を取得したい場合のメソッド
  return this.itemsReactiveData.snapshot();
}

その他の例

ReactiveData自体が何かのデータをsubscribeする必要がある場合もある。 任意の第二引数にsubscribeの方法を取れるようにした。

() => Observable<T>型として渡しているので、 ReactiveDataインスタンスを作成した時にsubscribeを始める。

例1. APIの内容を取得する

let subscriptionItems: () => Observable<Items> = () => {
  return this.someApiService.getItems().pipe(
    map(data => new Items(data))
  );
}
// ...
let items = new ReactiveData<Items>(null, subscriptionItems);

例2. 現在のURLを取得する

// router: Router
let subscriptonPage: () => Observable<string> = () => {
  return this.router.events.pipe(
    filter(event => event instanceof NavigationEnd)
  );
}
// ...
let page = new ReactiveData<string>(null, subscriptonPage));

などなど

参考

サーバー設定手順 6. Angular

作成中のプロジェクトのディレクトリで

ng build --prod

エラーがなければトランスパイルされたコンパクトなファイルが dist/ 以下に作成される。

上記ファイルをサーバーの/var/www/html/以下などに設置。 angularはURLのリライトが必要なので、.htaccesshttpd.confを編集

.htaccessで設定する前に、httpd.confに下記の記述が必要

<Directory "/var/www/html">
#   Options Indexes FollowSymLinks
    Options FollowSymLinks


#   AllowOverride None
    AllowOverride All

    Require all granted
</Directory>

(特にAllowOverride Allの部分)

今回は、htaccessで設定することにする。

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . index.html [L]
</IfModule>

これを.htaccessとして/var/www/html/に設置。

サーバー設定手順 5. JVM,sbtの設定

APIとしてplayframework製のサーバーを設定する例。 playframeworkはScala(もしくはJava)のライブラリであり、ScalaJVM上で実行できる。 まず、JVMをインストールし、Scalaと、Scalaのビルドや実行を行うsbtをインストールする。

Java(OpenJDK)をインストール

ScalaではJava8(Java1.8)を利用する。

# yum install java-1.8.0-openjdk-devel.x86_64

# java -version
# javac -version
openjdk version "1.8.0_181"
OpenJDK Runtime Environment (build 1.8.0_181-b13)
OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)

javac 1.8.0_181

(-develがないものもあるが、そちらはjavacがないのでダメ)

scalaのインストール

Scala2.13は策定中なので2.12を利用する

# cd
# wget https://downloads.lightbend.com/scala/2.12.6/scala-2.12.6.rpm
# yum install scala-2.12.6.rpm

# scala -version
Scala code runner version 2.12.6 -- Copyright 2002-2018, LAMP/EPFL and Lightbend, Inc.

sbtのインストール

# cd
# curl https://bintray.com/sbt/rpm/rpm | tee /etc/yum.repos.d/bintray-sbt-rpm.repo
# sudo yum install sbt

# sbt
sbt:root> about
[info] This is sbt 1.2.1

(この方法の確認だとカレントディレクトリにproject, targetディレクトリが作成されるので注意,ただ他に確認の方法がない...) sbtはプロジェクトのあるフォルダで実行すると、ビルド定義ファイルを読み自動的にターゲットのバージョンのsbtをダウンロードして実行してくれる。

プロジェクトの設置

作成中のプロジェクトのフォルダ ywj-play-test をhome/miy/以下に設置

$ cd ~/my-play-project
$ sbt
[my-play-project] $ start -Dplay.evolutions.db.default.autoApply=true -Dapplication.secret=abcdefghijk
  • -Dplay.evolutions.db.default.autoApply=trueはevolutionsの設定
  • -Dapplication.secret=abcdefghijkも設定しないとstartは動かない
(Starting server. Type Ctrl+D to exit logs, the server will remain in background)

と表示される。Ctrl+Dをする前にsshdを閉じるとこのプロセスも道連れになるので注意。

$ psまたは$ ps xfでプロセスを確認。終了するにはkillで良いのだろうか。

firewalldの設定

このAPIではポート9000番を利用するので、firewalldの設定

# firewall-cmd --permanent --zone=public --add-rich-rule="rule family="ipv4" port protocol="tcp" port="9000" accept"
# firewall-cmd --reload

上記の確認は

# firewall-cmd --list-all

サーバー設定手順 4. CakePHPの設定

何かとCakePHPを利用することは多い + サーバーの設定が正しく出来ているかの確認が出来るため、 空のCakePHPのプロジェクトを設置する。

php7.2をインストール

CakePHPではphpバージョン7.2を利用する。 phpの最新は7.3だが、CakePHPでサポートされているわけではない。(2018/08/24現在)

phpがまだインストールされていないか確認

# php -v
-bash: php: command not found

インストール可能なphpの検索

# yum list available | grep php

これではphp7系は見つからないかもしれない。

# yum install epel-release
# yum install http://rpms.remirepo.net/enterprise/remi-release-7.rpm
# yum list available | grep php

たくさん種類が出てくると思う。

php7.2のインストール

# yum install php72 php72-php php72-php-intl php72-php-mbstring php72-php-pdo php72-php-mysqlnd

それぞれ説明

  • php72: php7.2の本体
  • php72-php: httpdphpを繋ぐ役割を果たす
  • php72-php-intl: cakephp3で必要な拡張
  • php72-php-mbstring: 日本語の文字列処理で必要な拡張
  • php72-php-pdo: データベース接続クラス(PHP Data Objectというらしい)
  • php72-php-mysqlnd: mysql専用の接続の拡張

php」でphp72を利用できるようにする

現状だと$ php72で利用できるが、$ phpで利用できるようにしたい。

# module load php72

確認

$ php -v
PHP 7.2.9 (cli) (built: Aug 15 2018 08:05:45) ( NTS )

.htaccessを有効化する(httpd)

CakePHPなどのフレームワークでは、どのURLからアクセスしてもルートにあるindex.phpにリライトしてリクエストしないといけない。 リライトするにはmod_rewrite.soというapacheの拡張が必要。

mod_reqrite.soが有効か確認

(httpd.confでconf.modules.dの一式がインポートされている前提)

cd /etc/httpd/conf.modules.d
ls | xargs cat | grep mod_rewrite.so
LoadModule rewrite_module modules/mod_rewrite.so

mod_rewrite.soはロードされている。

.htaccessを有効化

<Directory "/var/www/html">
#   Options Indexes FollowSymLinks
    Options FollowSymLinks


#   AllowOverride None
    AllowOverride All

    Require all granted
</Directory>

cakephpで利用するデータベースの設定

cakephp用のユーザーの作成と権限の確認

# mysql -u root -p
mysql> create user 'cakephp_test'@'localhost' identified by 'password';
mysql> show grants for 'cakephp_test'@'localhost';
+--------------------------------------------------+
| Grants for cakephp_test@localhost                |
+--------------------------------------------------+
| GRANT USAGE ON *.* TO `cakephp_test`@`localhost` |
+--------------------------------------------------+
1 row in set (0.00 sec)

上記は、cakephp_testユーザーには何も権限がない状態を示している

cakephp用のデータベースの作成と確認

mysql> create database cakephp_test;
grant all on cakephp_test.* to 'cakephp_test'@'localhost';
mysql> show grants for 'cakephp_test'@'localhost';
mysql> show grants for 'cakephp_test'@'localhost';
+------------------------------------------------------------------------+
| Grants for cakephp_test@localhost                                      |
+------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `cakephp_test`@`localhost`                       |
| GRANT ALL PRIVILEGES ON `cakephp_test`.* TO `cakephp_test`@`localhost` |
+------------------------------------------------------------------------+
2 rows in set (0.00 sec)

上記は、cakephp_testユーザーはcakephp_testデータベースの全ての権限を持っている

空のプロジェクトの配置

ローカルで空のプロジェクトの作成

$ composer create-project --prefer-dist cakephp/app cakephp_test

作成されたファイルのconfig/app.phpを編集(変更箇所のみ抜粋)

'Datasources' => [
    'default' => [
        'username' => 'cakephp_test',
        'password' => 'password',
        'database' => 'cakephp_test',

アップロード

cyberduck等を用いて上記プロジェクトファイルを/var/www/html以下にアップロード

パーミッションの変更

ローカルで作成したファイルはパーミッションがローカルのユーザー名などになっている可能性があるので変更

# chown -R root:webdev /var/www/html
# chmod -R 775 /var/www/html

上記設定後、ブラウザからアクセスすると、Welcome的なページが表示されるはずである

  • webroot/以下が読み込まれず、cssなどが読み込まれない場合
    • .htaccessが有効になっているか確認
  • ファイルにアクセス出来ないなどのエラーがたくさん出る
    • パーミッションの設定を正しく行ってもログファイルの書き込み権限などがない場合はSELinuxの問題かもしれない。

サーバー設定手順 3. MySQLの設定

どのバージョンを利用するかは要件によって異なるが、 新規のサービスであればmysql8.0を入れてしまいましょう

インストール

# yum list available | grep mysql

インストールできるものが見つからないかもしれない。その場合は下記を実行

# yum install http://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm
  • インストール
# yum install mysql-community-server

確認等

バージョン

# mysqld --version
/usr/sbin/mysqld  Ver 8.0.12 for Linux on x86_64 (MySQL Community Server - GPL)

常時起動設定

linux起動時にmysqldも起動するようにするには

# systemctl enable mysqld.service

systemctl status mysqld.service で常時起動がenabledになっていることを確認する。

初回起動をしたのち、パスワードの設定をする

初回起動

# systemctl start mysqld.service

初回起動時にログに仮パスワードが設定される。

# cat /var/log/mysqld.log | grep password
2018-08-12T19:42:09.576512Z 5 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: yiodd,erh6F>

yiodd,erh6F> とあるのでこれをメモしておく。

パスワードの強度の設定の変更

バージョンの高いmysqlでは簡単なパスワード(文字数が少ない, 数字や記号が含まれていないなど)を設定することができないため、 あらかじめ設定で強度の低いパスワードでも設定できるようにする。(本来は設定しないけど)

  • 設定ファイル my.cnf の場所を調べる
# mysql --help | grep my.cnf

おそらく /etc/my.cnf であると思う。そのファイルの下の方に

validate_password.policy=LOW

と記述する。

パスワードを変更する

# mysql_secure_installation

先ほどメモしたパスワード、新しいパスワードを入力(今回はpasswordとした)。その他、色々設定

Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y
Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Disallow root login remotely? (Press y|Y for Yes, any other key for No) : n (本来はyの方が良い)
Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y