20201010のPHPに関する記事は15件です。

Laravelのファイル、フォルダの役割ついて

【概要】

1.Laravelのファイル

2.Laravelのフォルダ

1.Laravelのファイル

備忘録として残しておきます!
.editorconfig:エディタに関する設定ファイルです
.env:動作環境に関する設定ファイルです
.gitignore:Gitに関するファイルです
.artisan:php artisan serve(localhost:8000)等のコマンドです
.styleci.yml:コードチェッカーのファイルです
.composer.json:composerを利用関係です
.package.json:Javascriptのパッケージ管理ツールであるnpm利用関係です
phpunit.xml:テストプログラム関係です
server.php:サーバー起動時のプログラム関係です
webpack.mix.js:Javascriptのパッケージツールで使用
yarn.lock:yarnのパッケージマネージャが使うアプリが入っています

2.Laravelのフォルダ

app:アプリケーションのプログラムがまとめられています。アプリケーションを作成する際はここにscrpitファイルを作ります。User.php(ユーザー認証設定),Http(アプリケーションにアクセスした際に処理をし、Controllerファイルがあります。)
bootsrap:アプリケーションを起動する際に一番最初に処理が行われます。
config:設定関連ファイルです
database:データベース関連ファイルです
public:JavascriptやCSSなどブラウザで検証ツールで見れる部分になります
resources:リーソスファイルであり、テンプレートファイル等があります
routes:ルーティング設定はここになります。web.phpがrubyでいうroutes.rbファイルにあたります。このフォルダの中身には、api.php.channels.php、console.phpがあります
storage:ログファイルが保存されます
tests:単体テスト
vendor:フレームワーク(ここではLaravel)のプログラムが入っています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】Valitronを用いたvalidation実装

php初心者の頃はよくわからなかったけど使っていくうちに使いやすさに惚れたバリデーションライブラリです。

環境

・PHP 7.3.18
・composerインストール済み

導入

composerを用いてインストールします。
(公式github:https://github.com/vlucas/valitron#installation)

php composer.phar require vlucas/valitron

実装

・入力フォーム等でPOST送信がされた際に以下実行
・$_POST送信で送られてきたname属性を指定します。

$v = new \Valitron\Validator($_POST);
//'required'で入力必須項目を指定、message()にてエラー時の出力文字を指定します
$v->rule('required', ['name', 'nameKana', 'phoneNumber', 'emailAddress'])->message('※{field}が未入力です。');
$v->rule('required', 'agreement')->message('※{field}の同意にチェックを入れてください。');
$v->rule('required', 'gender')->message('※{field}を選択してください。');

//上記'required'部分をemailにすることでメールアドレスの指定も可能
$v->rule('email', 'emailAddress')->message('※{field}を正しく入力してください。');
//'regex'で数字を指定
$v->rule("regex", "phoneNumber", "/\A0[0-9]{9,10}\z/")->message('※{field}を正しく入力してください。');

//validationエラー時、上記で指定したname属性部分を予め日本語にしておけば日本語で出力されます。
$v->labels([
  'name' => 'お名前',
  'nameKana' => 'フリガナ',
  'emailAddress' => 'メールアドレス',
  'gender' => '性別',
  'phoneNumber' => '電話番号',
  'agreement' => '個人情報保護方針'
]);
//上記続き
if ($v->validate()) {
  //バリデーションOK時の挙動(送信確定画面に遷移等)
}else{
  $errors = $v->errors();
  //バリデーションNG時の挙動(再入力画面に遷移等)
}

validationエラー時、$errorsは配列で返ってくるのでhtml側でforeachで回して表示します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue + Wordpress + Heroku + S3でポートフォリオを構築する

1. はじめに

こんにちは。ツダと申します。私はカメラが趣味で、自分の写真のポートフォリオサイトを作成したいと思い、Vue.jsとWordpressを使って作成しました。

Tsuda Work

この記事では、私がポートフォリオを作るうえで行ったことについて紹介させていただければと思っています。

2. 技術スタック

  • Vue.js : @vue/cli 4.5.6
  • Wordpress : 5.5.1
  • PHP : 7.3.5
  • heroku : heroku/7.44.0 win32-x64 node-v12.16.2
  • S3

簡単なシーケンス図(トップ画面表示時)

Tsudawork.png

3. Wordpress環境を整える

3.1 概要

今回のポートフォリオでは、WordpressをAPIとして使用します。 ですので、フロントエンド側はVueを、バックエンド側はWordpressというような役割分担をするイメージです。

参考:WP REST API

3.2 Localbyflywheelをダウンロード

まずはWordpress環境を構築します。環境構築にはLocalbyFlywheelを用います
。環境構築方法については以下のサイトを参考にしていただければと思います。

3.3 DB設定

後述しますが、デプロイするサーバは「heroku」を使います。herokuで使用するDBとローカル環境で使用するDBが異なりますので、wp-config.phpに条件分岐を書きます。wp-config.phpは作成したWordpressプロジェクトのルートフォルダに配置してあります(Windowsの場合、C:\Users\<ユーザ名>\Local Sites\<作成したアプリ名>\app\public配下)

wp-config.php
...(省略)...

// ** MySQL settings - You can get this info from your web host ** //
if (
    @$_SERVER["SERVER_NAME"] === '<作成したWordpressのローカル環境のドメイン名>'
) {
    // ローカル環境の設定
    define( 'DB_NAME', 'local' );
    define( 'DB_USER', 'root' );
    define( 'DB_PASSWORD', 'root' );
    define( 'DB_HOST', 'localhost' );
} else {
    // heroku環境の設定
    $url = parse_url(getenv('CLEARDB_DATABASE_URL'));
    define('DB_NAME', trim($url['path'], '/'));
    define('DB_USER', $url['user']);
    define('DB_PASSWORD', $url['pass']);
    define('DB_HOST', $url['host']);
}
define( 'DB_CHARSET', 'utf8' );
define( 'DB_COLLATE', '' );

...(省略)...

参考:PaaS入門 〜Heroku + wordpress〜

3.4 セキュリティ面の設定

WordPressはあくまでAPIとしての役割で使用するため、WordPressサイトにはアクセスされたくありません。そこで、直接アクセスされるのを防ぐための各種設定を行います。

ログイン画面のパスを変更する

WordPressはログイン画面へのパスがデフォルトで "/wp-admin" として設定されています。しかし、これではログイン画面へのアクセスが容易にできてしまうので、これを変更するためのプラグイン「Login Rebuilder」が用意されています。

参考: Login Rebuilderの使い方|WordPressのログインURLの変更方法

サイトへのアクセスを制御する

次にサイトへのアクセスを制御します。何も設定していない場合、サイトへアクセスした際に記事が表示されてしまいますので、表示されないように設定します。

設定するためには、設定しているテーマのfunctions.phpに下記のコードを追加します。

functions.php
function access_restriction() {
    // 許可するアクセス
    // 管理画面
     if (is_admin()) {
         return;
    }
    // APIのアクセス
    if (strpos($_SERVER['REQUEST_URI'],'wp-json')) {
       return;
    }
    // ログイン画面
    if (strpos($_SERVER['REQUEST_URI'],'<Login Rebuilderで設定したログイン画面へのパス>')) {
       return;
    }
     wp_die('アクセスできません', 'アクセス拒否', array('responce' => 403));
 }
 add_action('init', 'access_restriction');

管理画面、APIへのアクセス、およびログイン画面へのアクセスは必要なので許可し、それ以外を拒否するように設定しています。実際にアクセスしてみると下のような拒否画面が現れます。

WS000021.JPG

参考:WordPress へのアクセス制限を functions.php で設定する

3.5 S3を使うための設定をする

Herokuは定期的にビルドを行っているため、アップロードした画像が消えてしまう現象が発生します。

herokuのリポジトリがdynoという単位で管理されており、Freeプランだと一定時間経過後にdynoが再起動される仕様になっているから。 - 【Rails6】herokuで投稿した画像が表示されない

そこでAWSの「S3」というサービスを用いることで画像が消える現象を回避していきます。Wordpressのプラグインを用いてアップロードする画像の格納先をS3に設定することで、外部に画像を保存します。

S3を使用するための準備をする

参考:WordPressの画像データをS3に保存する

S3のアクセス権限の設定

ブロックパブリックアクセスは下記のように設定。
WS000022.JPG
この設定によって以下の二つを達成することを確認しております。

  • WordPressからの画像アップロード
  • ポートフォリオサイトからの画像の表示

参考:Amazon S3 パブリックアクセスブロックの使用

バケットポリシーの設定は次のようにしています。

バケットポリシー
{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "AllowPublicRead",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "<リソース名>/*"
        }
    ]
}

参考:

ローカル環境でS3へつなげるようにする

私がローカル環境でテストを行った際、WordpressからS3につなげず、以下のようなエラーが発生しました。

Error Getting Bucket Region —There was an error attempting to get the region of the bucket tsudawork: Error executing "GetBucketLocation" on "<バケットのURL>?location"; AWS HTTP error: cURL error 60: (see https://curl.haxx.se/libcurl/c/libcurl-errors.html)

内容はCURLでS3へつなぐ際にコード60のエラーが発生しているというものです。コード60は次のようになっています。

CURLE_PEER_FAILED_VERIFICATION (60)
The remote server's SSL certificate or SSH md5 fingerprint was deemed not OK. This error code has been unified with CURLE_SSL_CACERT since 7.62.0. Its previous value was 51. - libcurl error codes

これだけでは少しわかりづらいですが、調べていると次のような記述を発見しました。

OSにバンドルされているルート証明書などが古いので証明書の検証ができない様子。 - curl: (60) SSL certificate problem, verify that the CA cert is OK

私の解釈ですが、つなぎ先サーバのSSL証明書とつなぎ元端末のSSL証明書を検証した際、つなぎ元端末のSSL証明書が存在しないor古いために発生するエラーと考えられます。こちらは次の対策をすることで解決しました。

まず、次のサイトより最新の証明書(cacert.pem)をダウンロードします。

CA certificates extracted from Mozilla

次に作成したwordpressプロジェクトのconf/phpフォルダ配下にphp.ini.hbsというファイルがありますので、そのファイルの一番下に次のコードを追加します。

php.ini.hbs
[curl]
curl.cainfo = "<ダウンロードしたcacert.pemのパス>"

Wordpressプロジェクトを再起動し、WP Offload Media Liteの設定画面でS3のバケットを選択すると接続ができるようになっているはずです。

参考:
curl: (60) SSL certificate problem: unable to get local issuer certificate
cURL error 60: SSL certificate problem: certificate has expired

GDをComposerに追加する

WordpressでS3を使用するためにはGD(画像ファイルの操作ライブラリ)の使用が必要です。HerokuにはデフォルトでGDがインストールされてないため、Composerで使えるように設定する必要があります。

参考:Image Processing and GD

まず、composer.jsonに下記のように追記します。

composer.json
{
    "require": {
        "ext-gd": "*"
    }
}

作ったファイルはwordpressのルートディレクトリに配置します。続いて下記のコマンドを実行します。

$ composer install

終了後、composer.lockがcomposer.jsonと同じディレクトリに作成されていればOKです。後はHerokuへのデプロイ時にHerokuが自動的にライブラリをインストールします。

参考:Heroku PHP Support

3.6 Herokuへデプロイする

まずは下記の記事を参考に、Herokuが使える状態にします。

参考:Heroku初心者がHello, Herokuをしてみる

そして下記の記事を参考に、作成したWordpressプロジェクトをHerokuへデプロイします。

参考:PaaS入門 〜Heroku + wordpress〜

主に次のことをやる必要があります。

  • Wordpressのルートディレクトリで「git init」+「git commit -m "commit"」
  • Wordpress用のプロジェクトアプリ作成
  • composer.jsonおよびcomposer.lockの作成
  • DB作成
  • デプロイ

3.7 パーマリンクの設定

WP REST APIにリクエストを投げると、デフォルトの状態では404エラーが返ってくるそうです。そのためにパーマリンクの設定をしてあげます。

※私の環境でこの設定をしなかったところ、 管理画面からの記事の編集・投稿ができませんでしたので設定することがおすすめです。

設定方法

設定方法はいたって簡単です。Wordpressの管理画面左にあるタブから「設定→パーマリンク設定」と進み、共通設定の「投稿名」にチェックをいれるだけとなっております。

WS000030.jpg

これで404エラーの原因となるパーマリンク設定が解消されるはずです。

参考:WP REST APIで404が返ってくる。これはパーマリンク設定のせいだ!

4. Vue.jsでプロジェクトを作成していく

4.1 環境構築

まずVueの環境構築を行います。Vueを使用する方法は主に二つあります

  • CDN
  • Vue CLI

ここではVue CLIを用いた方法を選択します。「参考」に記載させていただいた記事をもとに、「Welcome to Your Vue.js App」が表示されればOKです。

参考:Vue.js を vue-cli を使ってシンプルにはじめてみる

4.2 必要なモジュールをインストール

  • axios
$ npm install axios
  • bootstrap-vue
$ npm install vue bootstrap-vue bootstrap
  • vue-router
$ npm install vue-router

参考:
axios - npm
Getting Started | BootstrapVue
Installation | Vue Router

4.3 アプリの全体像

アプリの全体像は次のようになっています。

main.js
import Vue from 'vue'
import Articles from './Articles.vue'
import Article from './Article.vue'
import Router from 'vue-router'
import Top from './Top.vue'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

// Router
Vue.use(Router)

// Install BootstrapVue
Vue.use(BootstrapVue)

// Optionally install the BootstrapVue icon components plugin
Vue.use(IconsPlugin)


const router = new Router({
  mode:'history',
  routes: [
    {
      path: '/',
      component: Articles
    },    
    {
      path: '/category/:value',
      name: 'category',
      component: Articles,
    },
    {
      path: '/post/:value',
      name: 'post',
      component: Article
    }
  ]
})

Vue.config.productionTip = false

new Vue({
  render: h => h(Top),
  router
}).$mount('#app')
Top.vue
<template>
  <div id="app">
    <b-navbar class="navbar bg-white" fixed="top" toggleable="lg">
      <b-navbar-brand href="/">Tsuda Work</b-navbar-brand>
      <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
      <b-collapse id="nav-collapse" is-nav>
        <b-navbar-nav>
          <b-nav-item-dropdown :text="category.name" class="nav-item" v-for="category in categories" :key="category.id">
              <b-dropdown-item><router-link class="nav-link" :to="{name:'category', params:{value:category.id}}">All</router-link></b-dropdown-item>
              <b-dropdown-item :text="subCategory.name" class="nav-item" v-for="subCategory in category.subCategories" :key="subCategory.id">
                <router-link class="nav-link" :to="{name:'category', params:{value:subCategory.id}}">{{subCategory.name}}</router-link>
              </b-dropdown-item>
          </b-nav-item-dropdown>
        </b-navbar-nav>
        <b-navbar-nav class="ml-auto">
          <b-nav-item class="nav-item" href="https://twitter.com/tsuda215">
            <div>Twitter</div>
          </b-nav-item>   
        </b-navbar-nav>
      </b-collapse>
    </b-navbar> 
    <router-view class="article-router" :key="$route.path"></router-view>
    <footer class="page-footer font-small blue pt-4">
      <div class="footer-copyright text-center py-3">© 2019-2020 Tsuda Work.</div>
    </footer>
  </div>
</template>

<script>
import axios from 'axios';
import states from "./assets/property.json";
export default {
  name: 'Top',
  data() {
      return {
        categories: [],
      }
  },
  mounted() {
    const url = states.hostname + states.categoriesUrl;
    (async () => {
      try {
        // カテゴリー取得
        const res = await axios.get(url);
        var categoriesTmp = [].concat(res.data);
        for (var categoryTmp of categoriesTmp) {
          // サブカテゴリ―の場合は処理をスキップ
          if (categoryTmp.parent > 0) {
            continue;
          }

          // カテゴリー作成
          var category = {};
          category['id'] = categoryTmp.id;
          category['name'] = categoryTmp.name;
          category['subCategories'] = [];

          // サブカテゴリー取得
          const resForSubCategories = await axios.get(url + "?parent=" + categoryTmp.id);
          var subCategoriesTmp = [].concat(resForSubCategories.data);

          for (var subCategoryTmp of subCategoriesTmp) {
            // サブカテゴリー作成
            var subCategory = {};
            subCategory['id'] = subCategoryTmp.id;
            subCategory['name'] = subCategoryTmp.name;
            // サブカテゴリ―配列に追加
            category['subCategories'].push(subCategory);
          }

          // カテゴリー配列に追加
          this.categories.push(category);
        }
      } catch (error) {
        console.log(error);
      }
    })();
  },
}
</script>

<style>
#app {
  display: flex;
  flex-direction: column;
  min-height: 82vh;
  font-family: "Yu Gothic","Yu Gothic UI","Meiryo UI", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "MS PGothic", sans-serif; 
}
.navbar {
  background-color:rgba(0, 0, 0, 0);
}
.page-footer {
  margin-bottom:0;  
}
@media screen and (orientation:portrait) {
  .article-router {
    margin-top:10vh;
  }
}
@media screen and (orientation:landscape) {
  .article-router {
    margin-top:7vw;
  }
  @media screen and (max-width: 800px){
    .article-router{
      margin-top:9vw;
    }
  }
}
</style>
Articles.vue
<template>
  <div id="app">
    <transition name="fade">
      <!-- フェードイン実装のためv-ifは必要 -->
      <div v-if="ok" class="album py-5">
        <div class="container">
          <div class="row">
            <div class="col-md-4 mb-4" v-for="post in posts" :key="post.title.rendered">
              <div class="card h-100 shadow-sm">
                <router-link class="nav-link" :to="{name:'post', params:{value:post.id}}">
                    <img class="card-img-top" :src="post._embedded['wp:featuredmedia'][0].source_url" alt="">
                </router-link>
              </div>
            </div>
          </div>
        </div>
      </div>
    </transition>
  </div>
</template>

<script>
import axios from 'axios';
import states from "./assets/property.json";
export default {
  name: 'Articles',
  data() {
      return {
        posts: [],
        // フェードイン実装のために必要
        ok: false
      }
  },
  mounted() {
    var categoryId = this.$route.params.value;
    var url = states.hostname + states.postsUrl;
    if (categoryId != undefined) {
      url = states.hostname + states.categoryUrl + categoryId + '&_embed';
    }
    (async () => {  
      try {
        const res = await axios.get(url);
        this.posts = this.posts.concat(res.data);
        // マウント時にok=trueを実施
        this.ok = true;
      } catch (error) {
        console.log(error);
      }
    })();
  },
}
</script>

<style>
.card {
  display: flex;
  justify-content: center; /*左右中央揃え*/
  align-items: center; /*上下中央揃え*/
}

.fade-enter-active, .fade-leave-active {
  transition: opacity 1s;
}

.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}
</style>
Article.vue
<template>
  <transition name="fade">
    <div v-if="ok" class="container">
      <div class="row">
        <div class="articleContainer">
          <div class="article">
            <h3 class="title">{{post.title.rendered}}</h3> 
            <div class="content" v-html="post.content.rendered">
            </div>
          </div>
        </div>
      </div>
    </div>
  </transition>
</template>

<script>
import axios from 'axios';
import states from "./assets/property.json";
export default {
  name: 'Article',
  data() {
      return {
        post: null,
        // フェードイン実装のために必要
        ok: false
      }
  },
  mounted() {
    var postId = this.$route.params.value;
    const url = states.hostname + states.postUrl + postId + '?_embed';
    (async () => {
      try {
        const res = await axios.get(url);
        this.post = res.data;
        // マウント時にok=trueを実施
        this.ok = true;
      } catch (error) {
        console.log(error);
      }
    })();
  },
}
</script>

<style>
.articleContainer {
  display: flex;
  justify-content: center; /*左右中央揃え*/
  align-items: center; /*上下中央揃え*/
  width: 100%;
}

.article > .title {
  text-align: center;
}

.article img {
  width: 100%;
}

@media screen and (min-width: 800px){
    .article{
        width:60%;
    }
}

@media screen and (max-width: 799px){
    .article{
        width:85%;
    }
}

/* フェードインの設定 */
.fade-enter-active
/* , .fade-leave-active */
{
  transition: opacity 2s;
}
.fade-enter
/* , .fade-leave-to */
{
  opacity: 0;
}
.fade-leave-active {
  display:none;
}

</style>
property.json
{
    "hostname":"https://<Wordpressプロジェクトのホスト名>/",
    "postUrl":"wp-json/wp/v2/posts/",
    "postsUrl":"wp-json/wp/v2/posts?_embed",
    "categoryUrl":"wp-json/wp/v2/posts?categories=",
    "categoriesUrl":"wp-json/wp/v2/categories"
}

※注意

  • ホスト名を間違えると"No 'Access-Control-Allow-Origin' header is present on the requested resource."という旨のエラーが出ることを確認しました。
  • httpsをhttpにして設定すると、Heroku上のVueから画像が取得できないことを確認しました。

4.4 アプリの解説

Navbar

Navbarを使用する前に、vueでbootstrapを使用できるようにする必要があります。

参考:Getting Started | BootstrapVue

インストールコマンドは次の通りです。

# With npm
npm install vue bootstrap-vue bootstrap

# With yarn
yarn add vue bootstrap-vue bootstrap

インストールした後はmain.jsにてbootstrapを使えるように設定しています。

main.js
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

...(省略)...

// Install BootstrapVue
Vue.use(BootstrapVue)

// Optionally install the BootstrapVue icon components plugin
Vue.use(IconsPlugin)

次にメニュー部分の実装に入ります。
参考:Navbar

Top.vue
<template>
  <div id="app">
    <b-navbar class="navbar bg-white" fixed="top" toggleable="lg">
      <b-navbar-brand href="/">Tsuda Work</b-navbar-brand>
      <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
      <b-collapse id="nav-collapse" is-nav>
        <b-navbar-nav>
          <b-nav-item-dropdown :text="category.name" class="nav-item" v-for="category in categories" :key="category.id">
              <b-dropdown-item><router-link class="nav-link" :to="{name:'category', params:{value:category.id}}">All</router-link></b-dropdown-item>
              <b-dropdown-item :text="subCategory.name" class="nav-item" v-for="subCategory in category.subCategories" :key="subCategory.id">
                <router-link class="nav-link" :to="{name:'category', params:{value:subCategory.id}}">{{subCategory.name}}</router-link>
              </b-dropdown-item>
          </b-nav-item-dropdown>
        </b-navbar-nav>
        <b-navbar-nav class="ml-auto">
          <b-nav-item class="nav-item" href="https://twitter.com/tsuda215">
            <div>Twitter</div>
          </b-nav-item>   
        </b-navbar-nav>
      </b-collapse>
    </b-navbar> 
    <router-view class="article-router" :key="$route.path"></router-view>
    <footer class="page-footer font-small blue pt-4">
      <div class="footer-copyright text-center py-3">© 2019-2020 Tsuda Work.</div>
    </footer>
  </div>
</template>

ポイントは次の部分です。

Top.vue
<b-nav-item-dropdown :text="category.name" class="nav-item" v-for="category in categories" :key="category.id">
    <b-dropdown-item><router-link class="nav-link" :to="{name:'category', params:{value:category.id}}">All</router-link></b-dropdown-item>
    <b-dropdown-item :text="subCategory.name" class="nav-item" v-for="subCategory in category.subCategories" :key="subCategory.id">
      <router-link class="nav-link" :to="{name:'category', params:{value:subCategory.id}}">{{subCategory.name}}</router-link>
    </b-dropdown-item>
</b-nav-item-dropdown>

親カテゴリはAllとして必ず作成するようにしています。そしてサブカテゴリの数だけループを回し、追加のドロップダウンアイテムを作成しています。カテゴリ作成ロジックは次のようにしています。

Top.vue
mounted() {
  const url = states.hostname + states.categoriesUrl;
  (async () => {
    try {
      // カテゴリー取得
      const res = await axios.get(url);
      var categoriesTmp = [].concat(res.data);
      for (var categoryTmp of categoriesTmp) {
        // サブカテゴリ―の場合は処理をスキップ
        if (categoryTmp.parent > 0) {
          continue;
        }

        // カテゴリー作成
        var category = {};
        category['id'] = categoryTmp.id;
        category['name'] = categoryTmp.name;
        category['subCategories'] = [];

        // サブカテゴリー取得
        const resForSubCategories = await axios.get(url + "?parent=" + categoryTmp.id);
        var subCategoriesTmp = [].concat(resForSubCategories.data);

        for (var subCategoryTmp of subCategoriesTmp) {
          // サブカテゴリー作成
          var subCategory = {};
          subCategory['id'] = subCategoryTmp.id;
          subCategory['name'] = subCategoryTmp.name;
          // サブカテゴリ―配列に追加
          category['subCategories'].push(subCategory);
        }

        // カテゴリー配列に追加
        this.categories.push(category);
      }
    } catch (error) {
      console.log(error);
    }
  })();
},

記事表示部分

記事の表示は次の部分です。

Articles.vue
<div class="container">
  <div class="row">
    <div class="col-md-4 mb-4" v-for="post in posts" :key="post.title.rendered">
      <div class="card h-100 shadow-sm">
        <router-link class="nav-link" :to="{name:'post', params:{value:post.id}}">
            <img class="card-img-top" :src="post._embedded['wp:featuredmedia'][0].source_url" alt="">
        </router-link>
      </div>
    </div>
  </div>
</div>

こちらもbootstrapの使用が必要になります。

フェードインの実装

記事の一覧を表示する際にフェードインを使用しています。
参考:Enter/Leave & List Transitions

フェードインの実装にはvueのtransitionという機能を使用しています。transitionを使用するためにはフェードさせたい要素をで囲む必要があります。

Articles.vue
<transition name="fade">
  <!-- フェードイン実装のためv-ifは必要 -->
  <div v-if="ok" class="album py-3">
...(省略)...
  </div>
</transition>

v-ifに指定の"ok"はデフォルトでfalseとなっています。

Articles.vue
data() {
    return {
      posts: [],
      // フェードイン実装のために必要
      ok: false
    }
},

そしてマウント時にtrueとすることで、要素を表示させます。

Articles.vue
mounted() {
...(省略)...
    (async () => {  
      try {
        const res = await axios.get(url);
        this.posts = this.posts.concat(res.data);
        // マウント時にok=trueを実施
        this.ok = true;
      } catch (error) {
        console.log(error);
      }
    })();
  },

表示の際は下記のcssを使用しているので、フェードインでの表示となります。

Articles.vue
<style>
...(省略)...
.fade-enter-active, .fade-leave-active {
  transition: opacity 1s;
}

.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}
</style>

Wordpress REST APIを用いて記事取得

記事を取得する際はWordpress REST APIにリクエストを投げますが、リクエストを投げる際はaxiosを使用しています。

Articles.vue
<script>
import axios from 'axios';
import states from "./assets/property.json";
export default {
  name: 'Articles',
  data() {
      return {
        posts: [],
        // フェードイン実装のために必要
        ok: false
      }
  },
  mounted() {
    var categoryId = this.$route.params.value;
    var url = states.hostname + states.postsUrl;
    if (categoryId != undefined) {
      url = states.hostname + states.categoryUrl + categoryId + '&_embed';
    }
    (async () => {  
      try {
        const res = await axios.get(url);
        this.posts = this.posts.concat(res.data);
        // マウント時にok=trueを実施
        this.ok = true;
      } catch (error) {
        console.log(error);
      }
    })();
  },
}
</script>

実際にリクエストを投げているのは次の部分です。

const res = await axios.get(url);

記事のIDを取得したときなど、単数を指定するものはObject、IDを指定せず記事を複数取得した場合はObjectの配列がres.dataには格納されています。後は取得したものを加工し、vueで描画すればOKです。

参考:《WordPress》2017年末にWP REST API で取得してVue.jsで描画するまでのまとめ。

4.5 アプリをHerokuにデプロイする。

「参考」に記載の記事をもとに、大まかには下記の作業を行う必要があります。

  • expressのインストール
  • server.jsの作成
  • デプロイ

これらの作業を完了しアプリを表示すると、Wordpressで作成したアプリに対してリクエストが飛んでいるはずです。無事記事の一覧が表示されていればOKです。

参考:Vue.jsで作ったアプリをHerokuにデプロイ

4.6 リロードの設定

Vueアプリはデフォルトでrouterのパスに「ハッシュ(#)」が含まれています。これはrouterを「historyモード」に変更することで削除することができますが、historyモードを使用することでページをリロードした際に、404エラーが返却されてしまいます(historyモードでない場合にはこの問題は発生しません)。

しかしながら一点問題があります。シングルページのクライアントサイドアプリケーションなので、適切なサーバーの設定をしないと、ユーザーがブラウザで直接 http://oursite.com/user/id にアクセスした場合に 404 エラーが発生します。- HTML5 History モード

しかし、適切な設定をすれば「ハッシュを削除しつつ、リロードにも対応する」ことが可能となります。

connect-history-api-fallbackをインストールする

npm install --save connect-history-api-fallback

参考:connect-history-api-fallback

server.jsにコードを追加する

server.js

var express = require('express');
var path = require('path');


+ const history = require('connect-history-api-fallback');


var serveStatic = require('serve-static');
app = express();


+ app.use(history());


app.use(serveStatic(__dirname + "/dist"));
var port = process.env.PORT || 5000;
app.listen(port);
console.log('server started '+ port);

こちらローカル環境では動作しておりませんが、Herokuでは動作することを確認しております。

参考:vue-routerのルーティングURLからハッシュを除去しつつ、URL直接指定でも表示させる(Node, Express)

5. その他

5.1 スリープ防止のため、スケジューラ作成

Herokuのサイトは一定時間が経過すると、スリープ状態に入ってしまうため、次にアクセスする際にサイトが表示されるまで時間がかかるという問題が発生してしまいます。ここではそれを防止する方法について紹介します。

参考:herokuを24時間稼働させる設定

herokuにスケジューラを追加

まずはアプリケーションフォルダ配下で下記コマンドを実行し、スケジューラのアドオンを追加していきます。

$ heroku addons:create scheduler:standard

下記のように表示されれば成功です。

Creating scheduler:standard on ⬢ <アプリ名>... free
 To manage scheduled jobs run:
 heroku addons:open scheduler

Created <スケジューラの名前>
Use heroku addons:docs scheduler to view documentation

ジョブを作成する

続いて下記コマンドを実行して、ジョブの作成画面を表示します。

$ heroku addons:open scheduler

すると下記のような画面が表示されるはずです。

WS000013.jpg

「Create Job」を押下します。

WS000018.jpg

すると下記のようなウィンドウが表示されますので、

Schedule : Every 10 minutes
Run Command : curl <サイトのURL>

を入力し、「Save Job」を押下します。
これでジョブの作成は完了です。後は時間をおいてもう一度サイトにアクセスしてみると、表示が遅い問題が解消されているはずです。

5.2 Wordpressでアップロードできる画像の大きさを上げる

HerokuにWordpressをデプロイした直後は2MBまでの画像しかアップロードすることができません。この最大値を上げるための設定を行います。

.user.iniの追加

Herokuにはphp.iniの設定を書き換える方法として.user.iniの作成を挙げています。
参考:Customizing Web Server and Runtime Settings for PHP

具体的には、まず下記のようなファイルを作成します。

.user.ini
post_max_size = 100M
upload_max_filesize = 100M

そしてこのファイルをWordpressのルートディレクトリに配置します。後はアプリをデプロイするとHerokuが自動で設定値を認識し、画像のアップロードサイズが増加しているはずです。

6. まとめ

以上がVue + Wordpressを用いてポートフォリオを作成した手順となります。初めてVueを用いてWebサイトを作成しましたが、APIの併用もしたことで大変勉強になりました。簡単にはなってしまいますが、以上で記事を締めさせていただこうと思います。最後に、参考にさせていただいた記事の執筆者の方々、本当にありがとうございます。そして勝手に引用させていただき恐縮です。

備考

今回紹介させていただいたポートフォリオは無料で作成することが可能となっております。しかしSSLを常用化したり、独自ドメインを設定したい場合は有料となってしまいます。

参考させていただいたリンクまとめ

Wordpress

Vue.js

その他

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アルゴリズム入門[最大公約数]

まずアルゴリズムの学習を始めるにあたって最初に考え方をインプットしておきましょう
まずコンピューターの処理の種類は四つしかありません
入力記憶演算出力のみです

アルゴリズムと聞くと難しいと思うかもしれませんが、この4つを組み合わせて最高の処理の形を探すだけです

同様にして処理の流れも三つしかありません
順次分岐繰り返しのみです

次に問題を解くコツ4つを見ていきましょう

  • 処理の区切りを考える
  • キリの悪い数字を疑う
  • 数値の変化を追う
  • 計算量は無視してとりあえず動くようにしてみる
  • より良い処理を考える

こんな感じですね

ここを抑えてユークリッドの互除法を考えていきましょう
そもそもユークリッドの互除法とは二つの最大公約数を求めるアルゴリズムです

二つの整数の大きい方から小さい方を引くことを二つの整数が等しくなるまで繰り返す
等しくなった値が最大公約数

ではまずなぜそうなるのかを考えましょう

10025の最大公約数を求めていきましょう
100=5^2*2^2
25=5^2ですね
つまり共通している5^2最大公約数
これを上のアルゴリズムで計算すると

100>25なので
100-25=75
75>25なので
75-25=50
50>25なので
50-25=25
25=25
なので最大公約数は25

ではなぜこうなるのかを解説していきます
縦25、横100の長方形から縦25、横25の正方形を取り除くことができるのでユークリッドの互除法です

* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *

しかしこれだと数が大きくなれば大きくなるほど、計算回数が増えてしまうため、
剰余を求めてやるのが先ほど紹介したコツにある、より良い処理を考えるというところになります

function euclidean(int $x, int $y) {
    if ($x < $y) return euclidean($y, $x); 
    if ($y == 0) return $x;
    return euclidean(abs($y), intval($x % $y));
}

まず$x >= $yの状態を作りたいので$xの方が$yよりも小さい場合は数値を入れ替えて計算しなおします
また$y0の場合も必ず$xになる上に割る数のエラーが出てしまうので$xを返しています

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelのSeederで混乱した話

はじめに

自分のスキル感

実務でLaravel,PHPを触り始めて4ヶ月程度
バリバリの駆け出しエンジニアです。

実務でseederを扱って混乱したことがあるので、メモ書き程度にまとめておきます。
同じような境遇に人の役に立てば幸いです。

独学で勉強している時はあまりなんとも思わなかったのですが、環境の差異で軽く混乱しました。
勉強しているは平気で本番環境のデータベースををmigrateしなおしたり、
データを流し直したりしまくっていましたが、実務ではそうはいきません。

(注)自分が経験した現場での話なので、やり方は多数あるかもしれませんので、その辺はご了承ください。

Laravelのseeder作成から反映させるまでの流れ

自分が実務で経験した本番環境のデータベースをデータを変更したりする場合の手順は以下の通り
  1. データを変更するseeder(ここでは適当にExampleSeeder.phpとする)を作成+変更部分を記述
  2. laravelの場合,DatabaseSeeder.phpへ作成したSeederファイルの追加(DatabaseSeeder.phpの中に含まれる元からあるSeederファイルを変更するという手もありますが、あまり良くないかも)
  3. コードが良さそうであれば、マージした後に、以下のコマンドで変更を加えたSeederを本番環境で流す
$ php artisan db:seed --class=ExampleSeeder

雑にまとめるとこんな感じだろうか。

どこで混乱したか

上記の流れで厄介なのが、2の手順である。

もし仮に2の手順を飛ばしてマージされたとしたら、、、どうなるのか。。(ガクブル)

本番環境では単独でseederファイルを流します。

ローカルはどうなるかというと、、、
変更部分がマージされた後に、masterブランチpullしてきて、下記のコマンドを叩きます。

$ php artisan migrate:fresh --seed

すると、、、
本番環境では反映されているデータの変更が、ローカルには反映されません。(ここではExampleSeederが流れない)
なぜか。

php artisan migrate:fresh --seed

上記コマンドで流れるseederとはDatabaseSeeder.phpに記載しているseederファイルだからです。
このコマンドを叩いたから全てのseederファイルが流れるわけではありません

ここが注意すべきところです。

1と3だけでも本番環境には反映させることができます。

しかし、ローカル環境でも変更を反映させるためには変更を加えたseederファイルをDatabaseSeeder.phpへ追加する必要があるのです。

こういうのが積み重なり、本番環境とローカル環境,develop環境など、環境ごとに差異が出てきます。
そして本番環境でしか起こり得ないバグなど、ローカルでは調査しづらいバグの根本的な原因にもなってくるわけです。
恐ろしい

Seederを使う人は注意しましょう。。。。

終わりに

最後まで読んでいただいてありがとうございます。
何か間違っていることがあればぜひぜひご指摘ください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

php.ini (memory) に 1時間持っていかれた話

目標ができ、アウトプットするために記事を書こうと思いました。

がんばるぞい!

環境


・PHP 7.3

・Composer 1.10.13

問題

個人開発をネタにしようと何を作ろうか悩みながら
composerでインストールしていたら
壁は突然現れました。

スクリーンショット 2020-10-10 15.17.40.png

まず、error内容をグーグル先生に伝えました。
そうすると php.ini の内容を変えましょうという助言をいただきました。

【PHP】composer require時にPHP Fatal error: Allowed memory size of XXX bytes exhaustedが表示されたら

記事を拝読しました。
記事に、メモリ上限値無制限とあって、怖いなと感じて素直に行動しませんでした。
ここが良くなかったです。自ら時間を捨てる結果となりました。

# まずは、memory_limitの設定値を確認してみました (記事通りの結果)
% php -r "echo ini_get('memory_limit').PHP_EOL;"
128M
# php.iniの場所を確認しました (MacOS)
% php -i | grep php.ini                              
Configuration File (php.ini) Path => /etc
Loaded Configuration File => /etc/php.ini

先程のerror文の中に

PHP Fatal error:  Allowed memory size of 1610612736 bytes exhausted (tried to allocate 4096 bytes)

とあったので
php.iniを見て疑問に思いました。

php.ini
memory_limit = 128M # 256M や 512Mにしてみましたが…

4096 byteとあったので
0.004MBくらいじゃん!128M(MB?)で何がいけないの?
と思っていました。

数値を適当に上げましたが
同じerror文が出ました。

ファイルの場所や参照している何かが違うのか?と
1時間以上、調べまくりました。

しかし、どうにもならないので
希望の光もなく、見覚えのある数字にしてみました。

php.ini
memory_limit = 4096M

としました。

そしたら、成功しました。

キモチェ〜〜〜〜というか
ファ?!という感じでした。  完

数時間後、この記事を書いている際

error文を翻訳してみました。

1610612736バイトの許容メモリサイズが使い果たされました
(4096バイトを割り当てようとしました)

とのこと。

コア php.ini ディレクティブに関する説明 ¶リソース制限

公式サイトを確認してもよくわからなかったので

4096MのMってMemoryのMなのかな〜
そんなことを考えながら
別件のメールサーバーいじってたら、
6時間も過ぎてしまいました。

ちょっとイジるつもりが詰まってしまって…

何もできていない、開発しないとッ!!

緩い日報のようになってしまいました。

今回の投稿で感じたことは
画像より文字の方がいいなと感じたので
スクリーンショットではなくて
これからは、メモなどにコピペしておこうと思います。

Markdown 覚えないと…

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Mass Assignment とは(laravel)

Webアプリケーションのアクティブなレコードパターンが悪用され、パスワード、許可された権限、または管理者のステータスなど、ユーザーが通常はアクセスを許可すべきでないデータ項目を変更するコンピュータの脆弱性です。

以下引用
https://csword.net/2018/07/04/mass_assignment_vulnerability_%E3%81%A8%E3%81%AF/

Web上から入力されてきた値を制限することで、不正なパラメータを防ぐ仕組みらしいです

laravelではEloquentで Mass Assignmentの対策されているとのこと

->パラメータ(パソコンの動作を変える為の外部から与える設定値)
->Eloquent(Laravelで提供されているデータ操作の為の機能)
Eloquentについての詳細↓
https://readouble.com/laravel/6.x/ja/eloquent.html
https://laravel.com/docs/6.x/eloquent#mass-assignment

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Moodle 3.9 マニュアル - 新しいコースを追加する

原文

内容

1 コースの追加
2 コースの削除
3 コースの並べ替え
4 コースリクエスト
4.1 コースリクエストを自動的に承認する
5 一括コース作成
6 コーステンプレート
6.1 既存のコースをテンプレートとして使用する
6.1.1 方法1
6.1.2 方法2
6.2 バックアップコースをテンプレートとして使用する
6.3 コーステンプレートプラグインの使用
7 関連項目
7.1 スクリーンキャスト
7.2 フォーラムディスカッション

1 コースの追加

デフォルトでは、通常の教師は新しいコースを追加できません。 Moodleに新しいコースを追加するには、管理者コース作成者、またはマネージャのいずれかの権限が必要です。コースを追加するには:

[サイト管理]リンクから、[コース]> [コースとカテゴリの管理]をクリックします

400px-26addcourse1.png
右側のカテゴリページで[新しいコース]をクリックします

コースを希望するカテゴリをクリックします。詳細については、コースカテゴリを参照してください
「新しいコース」リンクをクリックします
コース設定を入力し、「保存して戻る」を選択してコースに戻るか、「保存して表示」を選択して次の画面に進みます。

newcoursesavereturn.png

次の画面で、[保存して表示]を選択した場合は、コースに割り当てる生徒/教師を選択します。

2 コースの削除

教師はコースを削除できません。管理者(つまり、ケーパビリティ:moodle/course:deleteが許可されている役割を持つユーザー)はコースを削除でき、コース作成者は自分で作成したコースを削除できますが、コースを作成してから24時間以内です。これは、誤って作成されたコースを管理者に依頼することなく削除できるようにするためです。

管理者はいつでもコースを削除できます。

コースを削除するには(管理者またはマネージャーとして):

1.[サイト管理]リンクから、[コース]> [コースとカテゴリの管理]をクリックします
2. コースのカテゴリをクリックし、右側の画面でコースをクリックします。
3. [削除]リンクをクリックします。

200px-26coursedelete.png
コースの削除

次の方法で複数のコースを削除できます。

・新しい(一時的な)カテゴリを作成します。 「削除予定」という名前を付けることができます。
・「削除予定」コースを選択して、そのカテゴリ(「削除予定」)に移動します。
・カテゴリを削除し(「削除予定」)、「すべて削除-元に戻せません」を選択します。

コース作成者が作成したコースを削除するためのユーザーインターフェイスはありません。ただし、コースのURLをhttp://yourmoodlesite.net/course/view.php?id=N から http://yourmoodlesite.net/course/delete.php?id=N に編集することでこれを行うことができます(置換「表示」と「削除」)。

3 コースの並べ替え

コースは、管理者または管理者が[管理]> [サイト管理]> [コース]> [コースとカテゴリの管理]から並べ替えることができます。

コースは、昇順/降順および作成された時間で並べ替えることができます。

200px-coursesort.png
コースの並べ替え

4 コースリクエスト

コースリクエスト機能は、管理者が[管理]> [サイト管理]> [コース]> [コースリクエスト]で有効にできます。

管理者は、コースリクエストのデフォルトのカテゴリ、ユーザーがコースをリクエストするときにカテゴリを選択できるかどうか、コースリクエストの通知を受け取ることができるユーザーを設定できます(ケーパビリティmoodle/site:approvecourseを持つユーザーのリストから)。

「新しいコースをリクエスト」ボタンが「すべてのコース」ページに表示されます。 [すべてのコース]ページには、コースブロックのリンクからアクセスできます。または、新しいコースページ https://yourmoodlesite.org/course/request.php をリクエストするためのリンクを(HTMLブロックやページリソースなどで)作成することもできます。

コースリクエストが有効になっている場合、デフォルトでは、認証されたすべてのユーザーがコースリクエストを行うことができます。コースリクエストを行うことができるユーザーを制限する方法の詳細については、コースリクエストの役割を参照してください。

4.1 コースリクエストを自動的に承認する

プラグインの自動承認により、Learn Moodle Basics MOOCのように、コースを自動的に承認することができます。

5 一括コース作成

コースを一括作成してコーステンプレートを使用する方法の詳細については、コースのアップロードを参照してください。

  1. [管理]> [サイト管理]> [コース]> [コースのアップロード]に移動します。
  2. ドラッグアンドドロップするか、ボタンを使用してファイルピッカーから選択することにより、CSVファイルをアップロードします。
  3. インポートオプションを選択し、[プレビュー]をクリックします。設定に問題がない場合は、[アップロード]をクリックします。

6 コーステンプレート

[管理]> [サイト管理]> [コース]> [コースのアップロード]から、将来のコースのテンプレートとして使用するコースと設定を指定できます。

新しいコースのデフォルトブロックは、config.phpで構成することもできます。

6.1 既存のコースをテンプレートとして使用する

まず、テンプレートとして使用したいコースをMoodleで作成または検索し、その短縮名をメモします。テンプレートコースには、たとえば、一貫性を保つためにサイト全体で使用される共通の見出し、セクションの概要、またはポリシー契約が含まれる場合があります。

コースの内容のみが復元されることに注意してください。その設定ではありません。設定は別途追加されます。

6.1.1 方法1

csvファイルを作成します。受け入れられるフィールドについては、コースのアップロードを参照してください。
[サイト管理]リンクから、[コース]> [コースのアップロード]をクリックして、ファイルを追加します。
プレビューして、[コースプロセス]まで下にスクロールします。 [アップロード後にこのコースから復元する]ボックスに、テンプレートコースの短縮名を追加します。
同じテンプレートを使用して複数のコースを作成していて、それらすべてに同じ設定を持たせたい場合は、デフォルトのコース値でこれらを指定できます。
csvファイルをアップロードします。

400px-template1.png
テンプレートコースの短縮名を追加

300px-26defaultcoursevalues.png
デフォルトのコース値

6.1.2 方法2

作成したcsvファイルですべての設定と選択したテンプレートコースを指定できます。選択したテンプレートコースのフィールドとしてtemplatecourseを使用し、コースの短縮名を使用します。含めることができるフィールドの完全なリストについては、コースのアップロードを参照してください。
テンプレートとして使用するコースを復元するためのcsvファイルの例を次に示します。たとえば、テーマ、言語、登録方法、生徒と教師の別名を指定します。

shortname,fullname,category,newsitems,theme,lang,format,enrolment_1,enrolment_1_role,role_student,role_teacher,templatecourse
Knit,Knitting,2,0,afterburner,fr,topics,manual,student,NewKnitter,MasterKnitter,OMT

[管理]> [サイト管理]> [コース]> [コースのアップロード]に移動し、csvファイルを追加します

6.2 バックアップコースをテンプレートとして使用する

[サイト管理]> [管理]> [コース]> [コースのアップロード]でcsvファイルを使用してコースを作成するプロセス中に、テンプレートとして使用するバックアップファイルを指定してアップロードすることができます。

csvファイルを作成します。受け入れられるフィールドについては、コースのアップロードを参照してください。
[サイト管理]> [管理]> [コース]> [コースのアップロード]から、ファイルを追加します。
プレビューして、[コースプロセス]まで下にスクロールします。 [アップロード後にこのファイルから復元する]ボックスに、テンプレートとして使用するバックアップファイルを追加します。
同じテンプレートを使用して複数のコースを作成していて、それらすべてに同じ設定を持たせたい場合は、「デフォルトのコース値」セクションでこれらを指定できます。
または、csvファイルでコース設定(形式、登録方法、言語など)を指定することもできます。この方法は、同じテンプレートを使用する異なるコースに異なる設定を持たせたい場合に役立ちます。
csvファイルをアップロードします。

400px-template2.png
テンプレートコースのアップロード

6.3 コーステンプレートプラグインの使用

emeneoのコーステンプレートプラグインを使用すると、テンプレートベースのコースを非常に簡単に作成できます。詳細とプラグインパッケージはここにあります:https://moodle.org/plugins/local_course_templates

7 関連項目

7.1 スクリーンキャスト

一括コース作成

7.2 フォーラムディスカッション

一部のユーザーまたは学生フォーラムディスカッションからの非表示

カテゴリコース

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

配列のキーが連番でないときに、値を挿入する方法

配列のキーが連番じゃない

配列のキーが連番じゃなくて良い感じに値を入れてあげたい時。

$array = array(0=>'a', 1=>'b', 3=>'d');
$array += array_fill(0, max(array_keys($array)), 'c');
ksort($array);

実行結果

array(4) {
  [0]=>
  string(1) "a"
  [1]=>
  string(1) "b"
  [2]=>
  string(1) "c"
  [3]=>
  string(1) "d"
}

こんな風に自然な配列の形に整形できます。

$array += array_fill(0, max(array_keys($array)), '空です。');

実行結果

array(4) {
  [0]=>
  string(1) "a"
  [1]=>
  string(1) "b"
  [2]=>
  string(12) "空です。"
  [3]=>
  string(1) "d"
}

こんな風にすれば抜けていた配列の判別も簡単にできます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】TSVファイルを配列に変換する

webサイトなどでカテゴリを設定・呼び出す際に必要なときがちょいちょいあるかと思います。

環境

PHP 7.3.18

tsvデータ例

スプレッドシートなどで下記のような表を作成
※一番左の列に項目を、右に列でデータを追加していく

~/config/member.tsv

name 田中 斎藤
id tanaka saito
email tanaka@~ saito@~
age 24 31
from tokyo saitama

実装

hash.php
toHash(~/config/member.tsv,"\t");

function toHash($filePath,  $format = false, $first = false)
{
    //formatはtsvしか許可しない
    if (!($format == "\t")) {
        return false;
    }

    //ファイルの存在判定
    if (!file_exists($filePath)) {
        return false;
    }

    $fl = file($filePath);
    $data = array();
    foreach ($fl as $k => $v) {
        $column = explode($format,  $v);
        if ($k == 0) {
            foreach ($column as $i => $j) {
                $title[$i] = trim($j);
            }

            if ($first == false) {
                continue;
            }
        }

        foreach ($column as $i => $j) {
            if ($i == 0) {
                $key = $j;
            } else {
                $data[$key][$title[$i]] = trim($j);
            }
        }
    }
    return $data;
}

結果

array(4) {
  ["id"]=>
  array(2) {
    ["田中"]=>
    string(6) "tanaka"
    ["斎藤"]=>
    string(5) "saito"
  }
  ["email"]=>
  array(2) {
    ["田中"]=>
    string(6) "tanaka"
    ["斎藤"]=>
    string(5) "saito"
  }
  ["age"]=>
  array(2) {
    ["田中"]=>
    string(2) "24"
    ["斎藤"]=>
    string(2) "31"
  }
  ["from"]=>
  array(2) {
    ["田中"]=>
    string(5) "tokyo"
    ["斎藤"]=>
    string(7) "saitama"
  }
}

各行ごとに配列で返ってきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Moodle 3.9 マニュアル - カテゴリ:コース

原文

カテゴリ:コース

コース機能に関するドキュメントページのインデックス。
サブカテゴリ

このカテゴリには、合計3つのうち、次の3つのサブカテゴリがあります。
C

Course formant

F

Files

R

Resources

カテゴリ「コース」のページ

合計77ページのうち、次の77ページがこのカテゴリに含まれます。

アクティビティ

教師用クイックガイド

コース
コースのホームページ

質問
進捗状況の追跡

テキストの編集

ブロック

ユーザーのグループ化

リソース

A

Activities 日本語訳:アクティビティ
Activity chooser
Adding a new course

B

Blocks 日本語訳:ブロック
Buttons course format

C

Capabilities/block/course list:addinstance
Capabilities/block/course summary:addinstance
Capabilities/block/recent activity:addinstance
Capabilities/block/social activities:addinstance
Capabilities/enrol/category:config
Capabilities/enrol/database:config
Capabilities/enrol/guest:config
Capabilities/enrol/imsenterprise:config
Capabilities/enrol/manual:enrol
Capabilities/moodle/category:create
Capabilities/moodle/category:delete
Capabilities/moodle/category:manage
Capabilities/moodle/category:update
Capabilities/moodle/category:viewhiddencategories
Capabilities/moodle/course:activityvisibility
Capabilities/moodle/course:changecategory
Capabilities/moodle/course:changefullname
Capabilities/moodle/course:changeidnumber
Capabilities/moodle/course:changelockedcustomfields
Capabilities/moodle/course:changeshortname
Capabilities/moodle/course:changesummary
Capabilities/moodle/course:configurecustomfields
Capabilities/moodle/course:create
Capabilities/moodle/course:delete
Capabilities/moodle/course:ignoreavailabilityrestrictions
Capabilities/moodle/course:ignorefilesizelimits
Capabilities/moodle/course:manageactivities
Capabilities/moodle/course:managefiles
Capabilities/moodle/course:managemetacourse
Capabilities/moodle/course:movesections
Capabilities/moodle/course:recommendactivity
Capabilities/moodle/course:renameroles
Capabilities/moodle/course:request
Capabilities/moodle/course:sectionvisibility
Capabilities/moodle/course:setcurrentsection
Capabilities/moodle/course:update
Capabilities/moodle/course:viewhiddencourses
Capabilities/moodle/course:viewhiddensections
Capabilities/moodle/course:viewhiddenuserfields
Capabilities/moodle/course:viewparticipants
Capabilities/moodle/course:visibility
Capabilities/moodle/rating:rate
Capabilities/moodle/rating:view
Capabilities/moodle/rating:viewall
Capabilities/moodle/rating:viewany
Capabilities/moodle/site:import
Collapsed Topics course format
Course backup
Course categories
Course enrolment
Course FAQ
Course formats
Course homepage 日本語訳:コースのホームページ
Course list
Course overview (legacy)
Course relative dates
Course restore
Course settings
Courses 日本語訳:コース
Courses block

D

Download courses

E

Editing text 日本語訳:テキストの編集

G

Grouping users 日本語訳:ユーザーのグループ化

Q

Questions 日本語訳:質問

R

Recent activity block
Resources 日本語訳:リソース
Restrict access
Reusing activities

S

Stealth activities

T

Teacher quick guide 日本語訳:教師用クイックガイド
Tracking progress 日本語訳:進捗状況の追跡

U

Upload courses

カテゴリ:先生 | 管理者

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】Collectionのpluckメソッド

はじめに

今回もLaravelのCollectionのメソッドについてまとめたいと思います。

pluckメソッドで指定したキーの配列を取得する

例えば、nameの値のみをを取得する場合下記のような実装になります。

$collection = collect([
    ['id' => 1, 'name' => '山田', 'age' => 18],
    ['id' => 2, 'name' => '佐藤', 'age' => 38],
    ['id' => 3, 'name' => '小林', 'age' => 25],
]);

$names = $collection->pluck('name');

print_r($names->toArray());

実行結果は下記になります。

Array
(
    [0] => 山田
    [1] => 佐藤
    [2] => 小林
)

pluck('id', 'name')のようにidとnameなど複数指定するとその指定した値のみを配列にして取得することもできます。

おわりに

いかがでしたでしょうか。
指定したキーの配列を取得をして試してみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Moodle 3.9 マニュアル - アクティビティの再利用

原文

アクティビティの再利用

コースまたはコースの一部を再利用するには、いくつかの方法があります。それらのほとんどは、バックアップと復元のプロセスに関係するか、類似しています。

アクティビティのバックアップ
アクティビティの復元

コースデータのインポート-教師が教える別のコースに移動して、要素を現在のコースにインポートできるようにします。 1つのプロセスであることを除いて、バックアップと復元に似ています。
コースのリセット-基本的に、現在のコースのすべてのアクティビティをクリーンアップします。
リソース/アクティビティを複製する-アクティビティまたはリソースの[設定の編集]メニューの[複製]オプションを使用して、別のコピーを作成します。

関連項目

コースのバックアップ-コースの全部または一部を取得し、現在のサイトまたは別のMoodleサイトに複製する従来の方法(復元とともに)。
コースの復元-コースのバックアップの全部または一部を取得して、既存のコースに追加したり、新しいコースを作成したりできます。
共有カート-コース間でアクティビティを簡単に共有できるプラグイン。

カテゴリコース バックアップ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Moodle 3.9 マニュアル - 進捗状況の追跡

原文

Moodleで生徒の進歩を追跡するいくつかの方法があります。このページでは、コースで教師が利用できるものの概要を説明します。

内容

1 成績
2 コンピテンシー
3 アクティビティの完了
4 コース修了
5 バッジ
6 コースレポート
7 分析

1 成績

すべてのコースには独自の成績表があり、コース管理>成績表の設定からアクセスできます。課題クイズなどの一部のアクティビティでは、成績がこの成績表に返送されます。教師が成績表に直接成績を入力することも可能です。

成績表のスクリーンキャスト

2 コンピテンシー

コンピテンシーは、特定の主題関連スキルにおける学習者の理解または習熟度のレベルを表します。コンピテンシーベースの学習またはスキルベースの学習としても知られるコンピテンシーベースの教育(CBE)は、学生がこれらの能力を実証する評価および評価のシステムを指します。

3 アクティビティの完了

管理者がコース設定でアクティビティの完了を有効にしている場合、教師はコース項目ごとに、完了として登録する方法を指定できます。アクティビティに対してチェックマーク/チェックマークが表示されます。生徒は手動で完了のマークを付けるか、生徒が指定された基準を満たしたときにアイテムが自動的に完了として登録されます。これらは、リソースの表示、課題の送信、フォーラムへの投稿、またはその他の条件である可能性があります。教師は、コース管理>レポート>アクティビティ完了のアクティビティ完了レポートで、誰が何を完了したかについての概要を確認できます。

300px-studentviewactivitycompletion.png
学生の画面

300px-activitycompletionreport.png
教師の画面

4コース修了

アクティビティ完了の延長として、コース完了を有効にすると、指定された基準に従って手動または自動で、コースを正式に終了としてマークすることができます。
コース完了ステータスブロック
が追加されている場合、学生はコース中の進捗状況を確認できます。教師は、[コース管理]> [レポート]> [コース完了]から、コース完了に向けた学生の全体的な進捗状況を確認できます。

300px-studentviewcoursecompletionreport.png
学生の画面

300px-coursecompletionreport.png
教師の画面

5 バッジ

バッジは、手動で、またはコースのアクティビティ完了設定を使用して授与することができ、学生のやる気を引き出すための一般的な方法です。学生は、コースのさまざまな段階で、さまざまなレベルの進歩に対してバッジを授与される場合があります。

6 コースレポート

教師は、生徒の進捗状況を追跡するのに役立つ多数のコースレポートをコースで利用できます。上記のアクティビティレポートおよびコース完了レポート(これらの設定が有効になっている場合にのみ使用可能)に加えて、アクティビティレポート参加レポート、および一般的なコースログもあります。

7 分析

Moodle 3.4以降の分析機能は、リスクのある学習者をサポートするための詳細な説明的および規範的なレポートを提供します。

カテゴリコース

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Moodle 3.9 マニュアル - 成績

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む