テクメモ

備忘録

Vue3のSuspenseを使ってみた

Vue3のSuspenseについて興味があったので、試しに触ってみた内容をまとめます

Suspenseって?

非同期処理が解決されるまで、コンポーネントの代わりにフォールバックコンテンツをレンダリングする特別なコンポーネントです。 今まで、computedで変数を定義して、v-ifで表示制御していたのを簡単に書けるようにしたもののようです。

実際に書いてみた

親コンポネ

親コンポネでは以下のように書きます。

<template>
  <Suspense>
    <template #default>
      <ArticleList/>
    </template>
    <template #fallback>
      Loading...
    </template>
  </Suspense>
</template>

<script lang="ts">
import {defineComponent} from 'vue'
import ArticleList from "./components/ArticleList.vue"

export default defineComponent({
  components: {
    ArticleList
  },
})
</script>

Suspenseは、2つのスロットを持っています。

default

最終的にレンダリングするコンテンツ

fallback

defaultに定義したコンテンツの非同期処理が完了するまでのコンテンツ


今回作ったサンプルだと、子コンポネの非同期処理が終わるまではLoadingと表示されます。

子コンポネ

非同期処理を行う子コンポネでは、以下のようにかきます。

<template>
  <div class="card-wrapper">
    <div class="card" v-for="(article, key) in articles" :key="key">
      <h1>{{ article.title }}</h1>
      <div>{{ article.content }}</div>
    </div>
  </div>
</template>

<script lang="ts">
import {defineComponent} from "vue";

export default defineComponent({
  async setup() {
    const sampleArticles = [
      {title: '記事A', content: '記事Aの内容'},
      {title: '記事B', content: '記事Bの内容'},
      {title: '記事C', content: '記事Cの内容'},
    ]

    const fetchArticles = () => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(sampleArticles)
        }, 3000)
      })
    };
    const articles = await fetchArticles();

    return {
      articles
    }
  }
});
</script>

通常ではAPIから何らかのデータを取得して、取得したデータを表示すると思いますが 今回は、簡潔にするためにsetTimeoutで擬似的に非同期処理にしています。 ブログの記事一覧を引っ張ってくるAPIを叩いているイメージで書いています。


このように書くことで非同期処理が終わるまではLoadingと表示され、終わったら記事が表示されるようになります。 f:id:ryonnsui1201:20210329004426g:plain

エラーが発生した際のハンドリング

非同期処理が失敗することもあると思います。
その場合は、onErrorCapturedでエラーを補足し、エラーを表示します。
onErrorCapturedは子孫コンポーネントからエラーが捕捉されるときに呼び出されるライフサイクルフックです。

<template>
  <div class="card-list">
    <div v-if="error">
      {{ error }}
    </div>
    <Suspense v-else>
      <template #default>
        <ArticleList/>
      </template>
      <template #fallback>
        Loading...
      </template>
    </Suspense>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, onErrorCaptured } from 'vue'
import ArticleList from "./components/ArticleList.vue"

export default defineComponent({
  components: {
    ArticleList
  },
  setup(){
    const error = ref(null);

    onErrorCaptured((e) => {
      error.value = e
      return true;
    });

    return {
      error
    }
  }
})
</script>


f:id:ryonnsui1201:20210329011320g:plain

まとめ

以上、Suspenseの使い方でした。 とてもシンプルに非同期処理の際の表示処理を書けるので便利ですね。

参考

Suspense - new feature in Vue 3 - Vue.js Tutorials

Vue3 Composition APIのReactivity APIまとめ

vue3のComposition APIのrefとreactiveについて調べた際に、他のReactivity APIについても気になったので今回まとめました。
自分が理解できた箇所のみをまとめた浅い記事ですがご容赦ください。

reactive

オブジェクトをリアクティブなProxyオブジェクトにして返す関数です。
この際のリアクティブへの変換はディープコピーのようですが、返されるプロキシは元のオブジェクトと同一ではないようです。

const obj = reactive({count: 1})
console.log(obj.count) // 1

obj.count++

console.log(obj.count) // 2

ref

プリミティブな値をアクティブで可変なrefオブジェクトを返す関数です。
refオブジェクトにはvalueというプロパティがあり、これによってアクセスできるようです。

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

computed

ゲッター関数を受け取り、ゲッターからの戻り値に対して不変のリアクティブなrefオブジェクトを返す関数です。

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2
count.value++
console.log(plusOne.value) // 3

plusOne.value++ // error

ゲッター関数とセッター関数を使えば、書き込み可能なrefオブジェクトを作成することもできます。

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 100,
  set: val => {
    count.value = val - 1
  }
})

console.log(plusOne.value) // 101
plusOne.value = 100
console.log(count.value) // 99

readonly

オブジェクトまたはrefを受け取り、読み取り専用プロキシを返す関数です。
ネストされたプロパティも読み取り専用になるようです。

const original = reactive({ count: 0 })
const copy = readonly(original)

original.count++
console.log(copy.count) // 1

// 読み取り専用だから書き換えられない
copy.count++ // error

watchEffect

依存関係を事後的に追跡しながら関数をすぐに実行し、依存関係が変更されたときに関数を再実行します。

const count = ref(0)

watchEffect(() => console.log(count.value)) // 0

setTimeout(() => {
  count.value++ // 1
}, 100)

watch

Vue2でのwatchとほぼ変わらないようです。

const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count) => {
    sampleFunction(count)
  }
)

複数を監視対象にすることもできるようです。

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

unref

引数がrefの場合は内部値を返し、それ以外の場合は引数自体を返す関数です。

const refSample = ref(100)
const notRefSample = 10

unref(refSample) // 100
unref(notRefSample) // 10

toRef

リアクティブなオブジェクトのある特定のプロパティをrefオブジェクトに変換する関数です。

const state = reactive({
  hoge: 1,
  fuga: 2
})

const sampleRef = toRef(state, 'hoge')

sampleRef.value++
console.log(state.hoge) // 2

state.hoge++
console.log(sampleRef.value) // 3

toRefs

リアクティブなオブジェクトの全てのプロパティをrefオブジェクトに変換する関数です。

const state = reactive({
  hoge: 1,
  fuga: 2
})

const sampleRefs = toRefs(state)

state.hoge++
console.log(sampleRefs.hoge.value) // 2

sampleRefs.hoge.value++
console.log(state.hoge) // 3

sampleRefs.fuga.value++
console.log(state.fuga) // 3

isRef

値がrefオブジェクトであるかどうかを確認する関数です。

const sampleRef = ref(1)
const sampleReactive = reactive({hoge: 1})
const sample = 1

console.log(isRef(sampleRef)) // true
console.log(isRef(sampleReactive)) // false
console.log(isRef(sample)) // false

isReactive

値がreactiveによって作成されたProxyオブジェクトであるかどうかを確認する関数です。
readonlyによってラップしたreactiveオブジェクトでもtrueを返すようです。

const sampleRef = ref('hoge');
const sampleReactive = reactive({
  fuga: 'fuga'
});
const sample = 'piyo'
const sampleRefAsReadonly = readonly(sampleRef);

isReactive(sampleRef) // false
isReactive(sampleReactive) // true
isReactive(sample) // false
isReactive(sampleRefAsReadonly) // true

isReadonly

値がreadonlyかどうかを確認する関数です。

const sampleRef = ref("hoge");
const sampleReactive = reactive({
  fuga: "fuga"
});
const sampleReadonly = readonly(sampleReactive);

isReadonly(sampleRef) // false
isReadonly(sampleReactive) // false
isReadonly(sampleReadonly) // true

customRef

更新のトリガーを明示的に制御し、カスタムしたrefオブジェクトを返すことができる関数らしいです。

const outputLogRef = (value: string) => {
  return customRef((track, trigger) => ({
    get() {
      console.log(value)
      track()
      return value
    },
    set(newValue: string) {
      console.log(newValue)
      value = newValue
      trigger()
    }
  }))
}

const hoge = outputLogRef("")

hoge.value = "hoge" // hogeが出力される
hoge.value = "fuga" // fugaが出力される
const fuga = hoge.value // fugaが出力される

markRaw

オブジェクトをマークして、reactiveなProxyオブジェクトに変換しないようにする関数らしいです。

const hoge = markRaw({})
console.log(isReactive(reactive(hoge))) // false

shallowReactive

ネストされたプロパティ以外をリアクティブにして返す関数です。

const state = shallowReactive({
  hoge: 1,
  fuga: {
    piyo: 2
  }
})

// これはリアクティブ
isReactive(state.hoge)
// ネストされたプロパティはリアクティブじゃない
isReactive(state.fuga) // false

shallowReadonly

ネストされたプロパティ以外をreadonlyにして返す関数です。

const state = shallowReadonly({
  hoge: 1,
  fuga: {
    piyo: 2
  }
})

// これはreadonly
isReadonly(state.hoge)
// ネストされたプロパティはreadonlyじゃない
isReadonly(state.fuga) // false

toRaw

reactiveまたはreadonlyオブジェクトの元のオブジェクトを返す関数です。

const sample = {}
const reactiveSample = reactive(sample)

console.log(toRaw(reactiveSample) === sample) // true

終わりに

Compotion APIのReactivity APIをまとめてみました。
Vue2のComposition APIプラグインではまだ対応しておらず、全く見たことないものや
いつ使うのか全く分からないものもありましたが、適宜用途に合わせて使っていきたいと思います。

参考文献

API Reference | Vue Composition API

CompositionAPIのwatchとwatchEffectの違い - Qiita

Vue3 Composition APIのrefとreactiveをざっくりと理解する

導入

今週、社内でVue3についての勉強会がありました。
その中で、一番気になったComposition APIのrefとreactiveについて調べることにしました
自分が理解できた箇所のみをまとめた浅い記事ですがご容赦ください。

リアクティブとは?

refとreactiveの話をする前に、リアクティブとは何かということについて理解する必要があります。
リアクティブとは、「ある変数を書き換えた際に既に定められた関係性によって、他の変数が更新される / 事前に定めた動作が実行される」だと僕は理解しています。

簡単な例だと以下になります。

const sample = reactive({a: 1})
const sample2 = computed(() => sample.a + 100)

console.log(sample2.value) // 101

sample.a = 10

console.log(sample2.value)// 110

リアクティブなデータなので、sample.aを書きかえた際にsample2が更新されています。

refとreactiveの基本

ref

プリミティブな値をアクティブで可変なrefオブジェクトを返す関数です。 refオブジェクトにはvalueというプロパティがあり、これによってアクセスできるようです。

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

template内で使う場合は、refがレンダーコンテキストのプロパティとして返されるようで、valueプロパティでアクセスする必要はないようです。

<template>
  <div>{{ count }}</div>
</template>

<script>
  export default {
    setup() {
      return {
        count: ref(0)
      }
    }
  }
</script>

reactive

オブジェクトをリアクティブなProxyオブジェクトにして返す関数です。 この際のリアクティブへの変換はディープコピーのようですが、返されるプロキシは元のオブジェクトと同一ではないようです。

    const obj = reactive({count: 1})
    console.log(obj.count) // 1

    obj.count++

    console.log(obj.count) // 2

refとreactiveの違い

リアクティブ化できる値

reativeはプリミティブな値はリアクティブにできませんが
refは、プリミティブな値以外(配列やオブジェクト等)でもリアクティブにできるようです。

// reactiveはプリミティブな値をリアクティブにすることはできない
    const number = reactive(0)
    console.log(number) // undefined
    
// refはプリミティブな値以外(配列やオブジェクト等)でもリアクティブにできる
    const str = ref('hoge')
    const array = ref([1, 2, 3, 4])
    const obj = ref({a:1, b:1})

    console.log(isRef(str)) //true
    console.log(isRef(array)) //true
    console.log(isRef(obj)) //true

    console.log(obj.value.a) // 1
    obj.value.a = 100
    const sample =  obj.value.a = 100 + 1
    console.log(sample) // 101

リアクティブの消失

reactiveでリアクティブにしたデータを、リアクティブのまま分割して使うことは出来ないようです。 文章だとわかりにくいので、以下にサンプルのコードを書きます

count.ts(reactiveなオブジェクトをセットする関数)

import { reactive } from '@vue/composition-api'

export function setCountReactive() {
  const obj = reactive({
    count: 1
  })
  return obj
}

home.vue

<template>
  <div>
    <button @click="incrementA()">足す</button>
    {{ obj.count }}
    <button @click="incrementB()">足す</button>
    {{ count }}
  </div>
</template>

<script lang="ts">
import { setCountReactive } from './count'

export default {
  setup() {
    /// 通常パターン
    const obj = setCountReactive()

    function incrementA() {
      return obj.count++
    }

    // リアクティブが消失してしまうパターン
    let { count } = setCountReactive()

    function incrementB() {
      return count++
    }

    return {
      incrementA,
      incrementB,
      obj,
      count,
    }
  }
}
</script>

こういう場合に、リアクティブに扱いたい場合はToRefsを仕様して、refでラップしてあげればいいようです。

count.ts(reactiveなオブジェクトをセットする関数)

import { reactive, toRefs } from '@vue/composition-api'

export function setCountReactive() {
  const obj = reactive({
    count: 1
  })
  return toRefs(obj)
}

ただし、refでラップする場合はvalueプロパティでアクセスする必要があります。
home.vue

<template>
  <div>
    <button @click="incrementA()">足す</button>
    {{ obj.count }}
    <button @click="incrementB()">足す</button>
    {{ count }}
  </div>
</template>

<script lang="ts">
import {setCountReactive} from './count'

export default {
  setup() {
    /// 通常パターン
    const obj = setCountReactive()

    function incrementA() {
      return obj.count.value++
    }

    // リアクティブが消失してしまうパターン
    let {count} = setCountReactive()

    function incrementB() {
      return count.value++
    }

    return {
      incrementA,
      incrementB,
      obj,
      count,
    }
  }
}
</script>

refとreactiveの使い分け

ここまで調べて、refとreactiveはどのように使い分けるのか?という疑問がわきました。
まだ有用な使い分けの仕方はわかりませんが、refとreactiveの違いでも書いたように
プリミティブな値を使う場合はref、それ以外の場合はreactiveを使っておけば間違いはないのかなと思います。
ただ、「オブジェクトのある特定のプロパティだけリアクティブに扱いたい」などの場合はreactiveでtoRefsを使えばいいようです。

終わりに

今回は、refとreactiveについて書きました。
Vue3での変更点や便利そうな新機能など、まだまだキャッチアップできていない情報がたくさんあるので 今後も機会があれば、アウトプットしていきたいと思います。

参考

API Reference | Vue Composition API

Ref vs Reactive Vue3 Composition APIのリアクティブ関数の探究 / ref vs reactive Vue Composition API Deep in - Speaker Deck

Object.assignの挙動で勘違いしていたこと

知ってる人からしたら、なにをいまさらという感じかもしれませんが Object.assignの挙動に関して勘違いしていたことがあり、業務中にハマったので備忘録程度に書きます。

勘違い1:Object.assignの返り値

Object.assignの返り値について勘違いしていました。 引数で渡したオブジェクトを合成した新しいオブジェクトを返す

のではなく

第2引数以降に渡したオブジェクトを、第1引数に渡したオブジェクトに合成して返す

ものでした。 MDNにも

Object.assign() メソッドは、すべての列挙可能なプロパティの値を、1つ以上のコピー元オブジェクトからコピー先オブジェクトにコピーするために使用されます。戻り値としてコピー先オブジェクトを返します。

Object.assign() - JavaScript | MDN

と書いてありました。

よって、以下のように

const obj1 = { a: 1, b: 2 };
const obj2 = { b: 4, c: 5 };

const obj3 = Object.assign(obj1, obj2);

console.log(obj1); // { a: 1, b: 4, c: 5 }

console.log(obj3); // { a: 1, b: 4, c: 5 }

obj1もobj3も中身が書き換わった状態になっています。

勘違い2:引数で渡したオブジェクトがネストされているときの挙動

Object.assignはシャローコピーのため、第2引数以降に渡したオブジェクトがネストされていた場合、オブジェクト参照がコピーされるようです。

const obj1 = {a:1, b:2};
const obj2 = {b:3, c:4, d:{a:5, b:6}};   

Object.assign(obj1, obj2);

console.log(obj1);  // {a:1, b:3, c:4, d:{a:5, b:6}}

obj1.d.a = 999;  // obj1.d.aの値を更新する

console.log(obj1);  // {a:1, b:3, c:4, d:{a:999, b:6}}
console.log(obj2);  // {b:3, c:4, d:{a:999, b:6}} →オブジェクト参照がコピーされているため、obj2.d.aの値が変わっている

コピー先の変更についてコピー元に影響を及ぼしたくないときは ディープコピーしたオブジェクトをコピー元として渡すなりすればよさそうです。

おわりに

初歩的な内容にはなりましたが、このような勘違いしていたこと、業務中ハマった内容は 小さなことでも書いていきたいと思います。

参考

developer.mozilla.org

microCMS使ってみた

今回は以前から気になっていた、日本製Headless CMSであるmicroCMSを触ってみたのでそのことについて書きます

Headles CMSとは

Head (ビュー = 表示画面) less(ない) CMS(コンテンツ管理システム)で、表示画面がないCMSのことです。
今までのCMSは表示画面とコンテンツ管理が合わさったものでしたが、HeadlessCMSは表示画面が存在しないので、自分で自由に作成することができます。
また、サイトのある一部分のみコンテンツ管理することも可能です。 Contentfulなどが有名だと思います。

microCMSとは

導入でも書きましたが、microCMSは日本製のCMSです。
ドキュメントや、サポートまですべて日本語対応しているため、初めてHeadlessCMSを触ってみる場合には取り扱いやすいと思います。
また、完全に主観ですが管理画面が分かりやすいため、エンジニアだけでなくコンテンツを管理する編集者も使いやすいと思います。

実際に使ってみる

それでは実際にブログ記事を管理する想定で使ってみます。
アカウント作成などは省きます。

APIの作成

まずはAPIを作ります。
API名とエンドポイントを指定します。 f:id:ryonnsui1201:20200528230033p:plain

APIで取得できるデータの形式を指定します。
今回は、記事一覧を取得するAPI作成するためリスト形式にします。 f:id:ryonnsui1201:20200528232932p:plain

APIで管理する各フィールドを作成します。
シンプルに

  • 記事タイトル
  • 記事内容
  • 投稿日時

を作成しました。

f:id:ryonnsui1201:20200528233356p:plain

フィールドの種類はたくさんあるため、コンテンツの種類によって選択します。 f:id:ryonnsui1201:20200528233752p:plain

フィールドに対して、複数のコンテンツを参照させることができます。
例えば、記事に対して複数タグなどをつけたい場合は、タグを返すAPIを作成しコンテンツを公開することで、記事一覧のAPIのタグのフィールドに複数のコンテンツを参照させることができます。
f:id:ryonnsui1201:20200529005010p:plain

コンテンツ作成

APIは作成したので、コンテンツを作成します。
先程作成した、タグも複数紐付けることができています。
コンテンツは、予約公開することもできます。 f:id:ryonnsui1201:20200529005724p:plain

APIプレビューで、APIのレスポンスを簡単にみることができます。 f:id:ryonnsui1201:20200529010307p:plain

画面での表示

画面で表示させます。

APIキーを保護する

APIキーをenvファイルで保護します。

.envにAPIキーを記述

API_KEY=key

記事一覧ページ

記事を一覧表示するだけのページを作ります tailwindcssを使っています。

<template>
  <div class="max-w-screen-lg mx-auto mt-5 flex flex-col items-center">
    <div>
      <article-card
        v-for="article in articles"
        :key="article.id"
        :article="article"
      />
    </div>
  </div>
</template>

<script>
import axios from 'axios'
import ArticleCard from '~/components/ArticleCard.vue'

export default {
  components: {
    ArticleCard
  },
  async asyncData() {
    const { data } = await axios.get(
      'https://bloglog.microcms.io/api/v1/blogs',
      {
        headers: { 'X-API-KEY': process.env.API_KEY }
      }
    )
    return {
      articles: data.contents
    }
  },
  data() {
    return {
      articles: [],
    }
  }
}
</script>

記事タイトル、投稿日、タグ、記事の内容が表示されていることが確認できました。 f:id:ryonnsui1201:20200529022345p:plain

さいごに

紹介の部分でも書きましたが、microCMSは日本語のドキュメントが充実しているので、扱いやすいです。
早いペースで新機能が追加されたり、改善されているため今後もっと使いやすくなっていくのでは、と思います。

参考

microcms.io

microcms.io

LaraveのEloquentについて

LaravelのEloquentとは?

EloquentはActive RecordライクなORMで、DBとモデルを関連付けてデータ操作をすることが可能。

※Active Recordとは Active RecordはDBからデータを読み出すための手法。DBのtableまたはviewの1行が1つのクラスにラップされ、オブジェクトのインスタンスがそのデータベースの1つの行に結合される。SQLを意識せずにデータベースアクセスを行うことができる。

モデルの作成

Eloquentを利用するにはモデルが必要なのでモデルを作成する

php artisan make:model Task

モデルとDBのテーブルとの関連付けは、テーブル名を複数形、モデルを単数形で作成すると暗黙的に関連付けられる。

例) テーブル名 → tasks モデル名 → Task

テーブル名がスネークケースの場合は、モデルをキャメルケースで作成すると関連付けられる。

例) テーブル名 → task_sample モデル名 → TaskSample

上記2つを適用しない場合は、$tableプロパティで指定し関連付けることができる。

データ検索/更新などの基本メソッド

よく使う基本的なメソッドを以下にまとめる。

全件抽出 all

テーブルの全レコードを取得するメソッド 戻り値はCollectionで、Collectionの要素はModelクラスのインスタンス

$tasks = Task::all();

PrimaryKey指定での抽出 find,findOrFail

find

引数にPrimaryKeyを指定して、合致するレコードのみ取得する。 戻り値はModelのインスタンス

$task = Task:find(10);

findOrFail

findと似てるが、該当レコードが見つからなかった場合、ModelNotFoundExceptionを投げる

try {
       $task = Task::findOrFail(10);
} catch (ModelNotFoundException $e) {
        // 見つからなかった場合の処理
}

条件指定による抽出 whereXX

SQLのwhere区に相当する条件を引数に指定し、絞り込みを行えるメソッド。 XXにはテーブルのカラム名が入る。

$task = Task::whereTitle('タスクタイトル')->get();

レコードの登録 create save firstOrCreatre

create

配列を引数に指定して、レコードを登録できる。

Task::create([
    'title'  =>  'タスクタイトル',
    'content'  =>  'タスクの内容'
]);

save

対象のモデルのインスタンスを新規作成し、各カラムの値を設定し登録できる。

$task = new Task;
$task->title = 'タスクタイトル';
$task->content = 'タスクの内容';

firstOrCreatre

ある条件でレコードを抽出し、レコードが見つからない場合のみ新規登録する。

$task = Task::firstOrCreatre(['title' => 'タスクタイトル']);

レコードの更新 update

更新対象のインスタンスに対して、更新したいカラムと値の配列を引数に指定しレコードを更新できる。

$task = Task::find(1)->update(['title' => 'タスクタイトル2']);

レコード削除 delete, destroy

delete

削除対象のインスタンスに使用し、レコードを削除できる。

$task = Task::find(1);
$task->delete();

destroy

削除対象のPrimaryKeyを指定し、レコードを削除する。

Task::destroy(1);

関連があるテーブル群をまとめて操作する

DBのテーブルは他のテーブルと関連していることが多い。 例えば、ユーザー(users)は複数のタスク(tasks)を持っている、など。 Eloquentはテーブルの関係性を踏まえて処理することができるリレーションという機能を持っっている。 以下はユーザー(users)、タスク(tasks)、カテゴリー(categories)の例で記述していく。

1対1 hasOne, belongsTo

例)書籍(books)テーブルと書籍詳細(book_details)テーブル

第1引数に関連付けるモデル名、第2引数に内部キー、第3キーに外部キーを指定することができる。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
    public function detail()
    {
        return $this->hasOne(BookDetail::class);
    }
}

逆はbelongsToで指定することができる。

1対多 hasMany

例)ユーザー(users)テーブルとタスク(tasks)テーブル

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    public function tasks()
    {
        return $this->hasMany(Task::class);
    }
}

hasOneと同様に、第1引数に関連付けるモデル名、第2引数に内部キー、第3キーに外部キーを指定することができる。

逆は、belongsToで指定できる。

多対多 belongsToMany

例)タスク(tasks)テーブルテーブルとカテゴリ(categories)テーブル

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    public function categories()
    {
        return $this->belongsToMany(Category::class);
    }
}

hasOneと同様に、第1引数に関連付けるモデル名、第2引数に内部キー、第3キーに外部キーを指定することができる。

逆もbelongsToManyで指定できる。

多対多では、結果コレクションに中間テーブル情報が付与されてくる。 これを取得したくないときは、モデルに$hiddenを定義することで除外できる。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    // 中間テーブル情報を取得しない
    protected $hidden = ['pivot'];

    public function categories()
    {
        return $this->belongsToMany(Category::class);
    }
}

中間テーブルのカラムを取得したい場合はモデルに取得したいカラムを指定しておき、取得時にpivotプロパティにアクセスする。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    // 中間テーブル情報を取得しない
    protected $hidden = ['pivot'];

    public function categories()
    {
        return $this
            ->belongsToMany(Category::class, 'task_category_ref')
            ->withPivot('task_id', 'category_id')
    }
}

Has Many Through

テーブルをまたぐリレーションを行うことができる。 以下の例で考える。

        users
    ⇅          ⇅
  roles       tasks

rolesからusersを経由してtasksを取得したい時

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    public function tasks()
    {
        return $this->hasManyThrough(Task::class, User::class);
    }
}

第1引数にリレーションを行うモデル、第2引数には経由するモデルを指定する。

PHP勉強会に参加してきました!

昨日、PHP勉強会 #141に参加してきたので参加レポート的なものを書こうと思います。 PHP勉強会に参加するのは2回目です。

会場

f:id:ryonnsui1201:20190829103748j:plain

いつもどおり、会場はGMO Yoursです。

タイムテーブル

Time title
19:00 会場・飲み物の配布・名刺交換
19:30 オープニング
19:35 自己紹介タイム
20:10 メイン発表枠①
20:30 懇親会前半
21:00 メイン発表枠②
21:20 LT枠

タイムテーブルは大体こんな感じだったと思います。 始めて参加した時も感じましたが、自己紹介でお題を与えられそれに答えたり、発表の途中で懇親会を挟んだりすることで非常に和やかで楽しい雰囲気な勉強会になっていると思います。

ちなみに、今回の自己紹介のテーマは「この夏にハマったことは?」でした。

発表

いくつか自分が気になった発表をピックアップして紹介したいと思います。

PHP始めて1年、レガシーシステムにどう立ち向かっているか @mizuki_r

speakerdeck.com

一番最初の発表はメイン発表枠の@mizuki_rさん。 自社プロダクトのビジネスモデルと乖離したレガシーシステムにどう立ち向かっているかというお話でした。 特に、ビジネス側が想像しているシステム、開発側が想像しているシステム、実際に動いているシステムがバラバラで、温度感スケジュールが合わないという話は非常に共感しました(前職で同じようなことがあったので)

P++とは何だったのか @tadsan

www.pixiv.net

メイン発表枠2番目は@tadsanさん。 最近話題になったP++について、経緯と事の真相についてのお話でした。 話の主軸とは関係ないのですが、個人的には今当たり前のように使っているPHPの言語仕様がどのバージョンで追加されたのか。 バージョンアップによる言語仕様の変更がどのように行われているかを知れたのが学びになりました。

※@tadsanさんが書いたこちらのQiitaの記事もとても勉強になります。 qiita.com

ひとりで画面数1,000以上のSaaSを開発した方法 @Toshiyuki Tanakaさん

speakerdeck.com LT枠トップバッター、EC事業者向けの受注管理、物流倉庫向けの倉庫管理を統合し1つのシステムにしたサービスを提供している@Toshiyuki Tanakaさんの発表でした。 Webエンジニアが1人しかいない状況で、いかに開発していくかというお話でした。 UIを徹底的にパターン化しているというお話がとても興味深かったです。

ブラウザ上で動くPHP @niisantokyo @新倉 涼太さん

docs.google.com

LT枠3番目?か4番目の弊社 新倉の発表です。 個人的にちょっと動作確認したいときに、ブラウザ上で動く環境があれば便利だねというお話でした。

JupyterProject Jupyter | Homeに関しては僕も使ってみたいと思いました。

総括

今回で2回目の参加でしたが、非常に楽しく有意義でした。 PHP勉強会に参加されてる方は、PHPに対する愛みたいなものを非常に感じます。 僕もそのうちLTで発表できたらなと思っています。

次回は2019年9月25日(水)に行われるようです。 ぜひ皆さん参加しましょう!

phpstudy.doorkeeper.jp