CleverStyle CMSで簡単なブログを作成する例

注意:この記事は執筆時点で関連性があり、物事の現在の状態は完全ではなくわずかに異なる場合があります。



私は、近代的なフレームワークが開発をどのように複雑にするのか長い間疑問に思っていました。 もちろん、私は彼らが悪いと言う権利はありませんが、私は彼らを良いと呼ぶこともできません。 そして、それが理由です。彼らの目標は、開発の簡素化と高速化、そして何らかの形でプロジェクトを標準化して構造化することです。 しかし、私の謙虚な主観的な意見では、前半では反対の効果が得られることがあり、それ自体では何もせず、メインコードのみを提供する多くのコードが書かれています。 この記事は、 rrromka が行ったZend Framework 2ではなく、CleverStyle CMSの独自の開発を使用して、シンプルなブログを開発するタスクへの異なるアプローチの例です。



ちょっとした歴史



CleverStyle CMSを3年間開発しています。 私が仕事をしようとしていたことの複雑さと不便さのために、必要が生じました。 開発は、CMSを使用するプロジェクトと並行して、自由時間で実行されます(欠落している機能が追加されます)。 これは、実際に必要な機能が追加されていることを意味し、タスクがあり、それらが最も便利に使用できる形式で追加されています。 主なアイデアは、明白なものの作業を自動化することであり、非常に単純ではありませんが、標準的な動作に影響を与え、必要な方法で調整する機会が常にあります。 さて、 最後の記事は、 前回の記事に対する建設的な批判に対するhabrasocietyへの大きな感謝です。あなたは、いくつかの事柄に対する私の考えを再考し、より良いプログラマーになり、CleverStyle CMSをより良く変えてくれました。



環境



明らかに、アクセスの詳細を含むWebサーバーとデータベースが必要です。

インストールには、コンポーザーやその他のツールは必要ありません。アーカイバーを使用する必要もありません。 ディストリビューションを将来のサイトのルートに投げて、ブラウザで開きます。 このウィンドウが表示されます:

画像

すべてのフィールドに入力することで、使用できる環境が整います。 配布パッケージはそれ自体を解凍し、必要に応じてデフォルト設定を設定し、セキュリティ上の目的で自身を削除します。 すでにこの段階から単純さを確認できます。ftp/ sshを介して数万の小さなファイルをコピーする必要はありません。



将来のモジュールの構造



wikiでシステムの構造に精通することができますが、記事の過程で使用される部品に精通します。

モジュール自体はPostsクラスで構成されます。これはデータベースのラッパーであり、投稿を管理するためのシンプルなインターフェイスを提供します。 ユーザーが利用できる実際のページ、APIもあり、最終的にはエンジンに作業の詳細の一部を説明し、モジュールを自己分散ディストリビューションにアセンブルできるようにするいくつかのメタファイルがあります。

最初に、将来のモジュール用にcomponents / modules / MyBlogディレクトリを作成します。



Db



ブログの投稿には、タイトル、コンテンツ、著者、執筆日が含まれます。 グラフィカルツールの支持者として、PhpMyAdminで構造を準備し、エクスポートします。



CREATE TABLE IF NOT EXISTS `[prefix]myblog_posts` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user` int(10) unsigned NOT NULL, `title` varchar(1024) NOT NULL, `text` text NOT NULL, `date` bigint(20) unsigned NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      
      





汎用性のためにテーブルのプレフィックスは[prefix]



置き換えられ、代わりにCMSが必要なものを置き換えます。

ファイルコンポーネント/モジュール/ MyBlog / meta / install_db / posts / MySQLi.sqlを作成し、受信したSQLをそこに貼り付けます。 したがって、管理パネルにモジュールをインストールすると、データベースに必要なテーブルが作成されます。 同様に、ファイルコンポーネント/モジュール/ MyBlog / meta / uninstall_db / posts / MySQLi.sqlを作成します。



 DROP TABLE `[prefix]myblog_posts`;
      
      





MySQLiはデータベースエンジンの名前であり、これまでのところ唯一のものです。

postsはデータベースが関連付けられている任意の名前です(テーブルの一部がMySQL / MariaDBの1つのサーバーにあり、2番目がPostgreSQLの別のサーバーにあるという点まで、異なる目的のために複数にすることができます)。

components / modules / MyBlog / meta / db.jsonに名前の投稿を書きましょう:



 [ "posts" ]
      
      





それはすべてデータベースです。 テーブルは、モジュールがインストールされると作成され、モジュールが削除されると削除されます。



投稿クラス



クラスをコンポーネント/モジュール/ MyBlog / Posts.phpファイルに配置し、cs \ modules \ MyBlog名前空間に配置します-これにより、CMSは必要に応じてそれを見つけることができます。

非表示のテキスト
 <?php /** * @package MyBlog * @category modules * @author Nazar Mokrynskyi <nazar@mokrynskyi.com> * @copyright Copyright (c) 2013, Nazar Mokrynskyi * @license MIT License, see license.txt */ namespace cs\modules\MyBlog; use cs\Config, cs\Cache\Prefix, cs\DB\Accessor, cs\Language, cs\User, cs\CRUD, cs\Singleton; /** * Class Posts for posts manipulation * * @method static \cs\modules\MyBlog\Posts instance($check = false) */ class Posts { use CRUD, Singleton; /** * Cache object instance * * @var Prefix */ protected $cache; protected $table = '[prefix]myblog_posts'; protected $data_model = [ 'id' => 'int', 'user' => 'int', 'title' => 'text', 'text' => 'html', 'date' => 'int' ]; protected function construct () { /** * Save instance of cache object with prefix MyBlog (will be added to every item) */ $this->cache = new Prefix('MyBlog'); } /** * Required by abstract Accessor class * * @return int Database index */ protected function cdb () { return Config::instance()->module('MyBlog')->db('posts'); } /** * Get post * * @param int|int[] $id * * @return array|bool */ function get ($id) { if (is_array($id)) { foreach ($id as &$i) { $i = $this->get($i); } return $id; } $id = (int)$id; /** * Try to get item from cache, if not found - get it from database and save in cache */ return $this->cache->get("posts/$id", function () use ($id) { $data = $this->read_simple($id); if ($data) { $L = Language::instance(); $data['datetime'] = $L->to_locale(date($L->_datetime_long, $data['date'])); $data['username'] = User::instance()->username($data['user']); } return $data; }); } /** * Add post * * @param string $title * @param string $text * * @return bool|int Id of created post or <b>false</b> on failure */ function add ($title, $text) { $id = $this->create_simple([ User::instance()->id, $title, $text, TIME ]); if ($id) { /** * Delete total count of posts */ unset($this->cache->total_count); return $this->db_prime()->id(); } return false; } /** * Edit post * * @param int $id * @param string $title * @param string $text * * @return bool */ function set ($id, $title, $text) { $data = $this->get($id); $data['title'] = $title; $data['text'] = $text; if ($this->update_simple($data)) { /** * Delete cached item if any */ unset($this->cache->{"posts/$id"}); return true; } return false; } /** * Delete post * * @param int $id * * @return bool */ function del ($id) { if ($this->delete_simple($id)) { /** * Delete cached item if any, and total count of posts */ unset( $this->cache->{"posts/$id"}, $this->cache->total_count ); return true; } return false; } /** * Get posts * * @param $page * * @return int[] */ function posts ($page = 1) { $from = ($page - 1) * 10 ?: 0; return $this->db()->qfas( //Readable database, Query, Fetch, Single, Array "SELECT `id` FROM `[prefix]myblog_posts` ORDER BY `id` DESC LIMIT $from, 10" ) ?: []; } /** * Get total count of posts * * @return int */ function total_count () { return $this->cache->get('total_count', function () { return $this->db()->qfs( //Readable database, Query, Fetch, Single "SELECT COUNT(`id`) FROM `[prefix]myblog_posts`" ) ?: 0; }); } }
      
      







このクラスは、投稿とその総数をキャッシュするデータベースのラッパーです。 これは孤立したクラスであり、次のパブリックメソッドがあります。



クラス自体はアクセス権をチェックせず、入力されたデータの正確さのみをチェックします。

DBは読み取りモードと書き込みモードの両方で使用されます
この例では、これは重要ではありませんが、最初の操作の分離により、レプリケーションを使用して書き込みにメインデータベースを使用し、残りを読み取りに使用できるようになります。 この小さな分離は見込み客と行われます



コードは非常に簡単に記述されており、コメントも充実しており、IDEも同様に認識します。



ユーザーインターフェース



一般に、モジュール内のページのアドレスを記述する単純なjson構造がルーティングに使用されます。 ユーザーパーツのルーティングを記述するファイルコンポーネント/モジュール/ MyBlog / index.jsonを作成します。



 { "list" : [], "post" : [ "view", "add", "edit", "delete" ] }
      
      





したがって、パスは次のようになります。



したがって、モジュールディレクトリに次のファイル構造を作成します。



それらが上記で説明されているという事実のために-CMSは対応するページでそれらを呼び出します。

list.phpファイルの例:

非表示のテキスト
 namespace cs\modules\MyBlog; use cs\Config, cs\Page, h; $rc = Config::instance()->route; $page = 1; if (isset($rc[1]) && $rc[1]) { $page = (int)$rc[1]; } $Page = Page::instance(); $Posts = Posts::instance(); $total_count = $Posts->total_count(); $Page->content( h::{'a.cs-button-compact'}( h::icon('plus').'  ', [ 'href' => 'MyBlog/post/add' ] ) ); if (!$total_count) { $Page->content( h::{'p.cs-center.uk-text-info'}('  ') ); return; } $Page->title(' '); if ($page > 1) { $Page->title(" $page"); } $Page->content( h::{'section article.cs-myblog-posts'}( h::{'h1 a[href=MyBlog/post/$i[id]]'}('$i[title]'). h::div('$i[text]'). h::footer('$i[datetime], $i[username]'), [ 'insert' => $Posts->get($Posts->posts($page)) ] ). ( $total_count > 10 ? h::{'div.cs-center'}(pages($page, ceil($total_count / 10), function ($page) { return $page < 2 ? 'MyBlog' : "MyBlog/list/$page"; })) : '' ) );
      
      







名前空間は、ほとんどすべてのモジュールファイルで同じです。

Config::instance()->route



route-モジュール名を考慮せずにページパス要素のインデックス付き配列を取得できます。 この場合、ユーザーがどのページを開くかを決定するために使用されます。たとえば、 MyBlog/list/3



、配列['list', 3]



を取得します。 一般に、上記のファイルは表現+アクセス制御です。



API



はい、モジュール自体では外部APIを使用しませんが、それでも使用します(誰かに役立つかもしれません)。 最も簡単なことをしましょう-特定の投稿を管理します(たとえば、ページをリロードせずに投稿を編集する)。 components / modules / MyBlog / apiにいくつかのファイルを作成します。



ファイルの名前は自分自身について十分に語っていると思います。 最も単純なケースでは、構造はなく、index.jsonは必要ありません。リクエストの種類ごとにDELETE / GET / POST / PUTのインデックスファイルの束を作成するだけで、CMSはこれらのファイル自体を見つけます。 接尾辞は、index.jsonを使用してネストされた構造で同様の方法でAPIで使用できます。 APIリクエストの例:

POST API / MyBlog

{

「タイトル」:「ブログ投稿のタイトル」、

「テキスト」:「ブログ投稿コンテンツ」

}


Content-type:application / jsonを指定することを忘れない場合は、JSONを送信できます。

応答として、次のいずれか:

201作成

...

{

「Id」:「5」

}


または、コードとエラーメッセージ。

これを行うindex.post.phpファイルを次に示します。



 namespace cs\modules\MyBlog; use cs\Page; if (!isset($_POST['title'], $_POST['text'])) { error_code(400); return; } if ($post = Posts::instance()->add($_POST['title'], $_POST['text'])) { code_header(201); Page::instance()->json([ 'id' => $post ]); } else { error_code(500); }
      
      





Page::instance()->json()



使用すると、データ(配列など)をそのまま送信でき、メソッド自体がJSON文字列を作成して必要なヘッダーを追加します。 同じことがerror_code()



にもerror_code()



、エラーコードを渡すだけです。他のすべては自動的に行われます。



最後に



モジュールのインストールディストリビューションを構築するには、いくつかのサービス情報を使用してファイルコンポーネント/モジュール/ MyBlog / meta.jsonを作成します。



 { "package" : "MyBlog", "category" : "modules", "version" : "0.0.2", "description" : "Simple demo blog module", "author" : "Nazar Mokrynskyi", "website" : "cleverstyle.org/cms", "license" : "MIT License", "db_support" : [ "MySQLi" ], "provide" : "myblog", "optional" : [ "editor" ], "languages" : [ "" ] }
      
      





予想どおり- コンポーネント/モジュール /MyBlog/license.txtコンポーネント/モジュール /MyBlog/prepare.phpコンポーネント/モジュール/MyBlog/languages/Russian.jsonを追加して、投稿ページのタイトルを美しくし、名前をローカライズしますモジュール。

さて、すべての準備ができたら-CleverStyle CMSリポジトリから取得してサイトのルートに追加します。



エンジンがこのファイルへの呼び出しをインターセプトしないように、.htaccessに数行を追加します。

<ファイルbuild.php>

リライトエンジンオフ

</ファイル>



build.php



に移動し、モジュールを選択し、リストでMyBlogを選択して、ビルドをクリックします。

サイトのルートで、ファイルMyBlog_0.0.2.phar.php



を取得します。このファイルは、システムの別のコピーにインストールするために使用できます。



他にできること



ポストスタイリングが必要な場合:



この記事のために特別なテストリポジトリが作成れました。

最小限の付随コードが使用され、構造がどこにも単純ではない場合、このアプローチが私だけでなく魅力的であることを願っています。

ご静聴ありがとうございました。私は質問と建設的な批判に喜んでいます。



UPDカップルのスクリーンショット:

非表示のテキスト








UPD 11.11.2013: CRUD特性がエンジンの新しいバージョンに登場し、Postsクラスが編集され、使いやすくなりました。



All Articles