目次
Workflowモデルの基本動作
Workflowモデルの基本動作を、先ほどの会員情報登録を例に、実際のファイル構成を基に説明します。
まず押さえておきたいのは、Cejuno Frameworkにおけるファイル拡張子のルールです。
URLで直接呼び出されるページのみが「.php」となり、それ以外のPHPファイル、HTMLテンプレートファイル、各種処理ファイルは全て「.inc」として配置されます。
※コンフィグXMLやその他のリソースファイルには、別途固有の命名ルールがあります。
概要
私は、フレームワークを作りたい人間ではありません。
「人が考え、設計し、創造する余地を最大化する構造」をこの世界に残したい人間です。
Cejunoは、私自身の思想・経験・設計哲学をそのまま構造として具現化したプロダクトです。
特定の技術トレンドや市場要請に迎合するために生まれたものではありません。
20年以上にわたり、業務システム、Web、CMS、EC、マルチテナント、多言語、そして「なぜ開発は苦しいのか」という問いに向き合い続けた末に必然として到達した構造です。
「なぜ業務システムはここまで複雑で、非効率なのか」
「なぜ人は“本質ではない実装”に時間を奪われ続けるのか」
この根源的な違和感に対する、ひとつの答えとしてCejunoは設計されています。
画像2-1. ファイル構成で見るWorkflowモデルの動作
役割
- FounderCejunoの構想、思想、方向性、存在意義の定義
- ArchitectCejunoの全体構造設計、実行モデル、拡張思想、互換性戦略の設計責任者Cejunoにおける設計判断・思想的決定は、すべて私の責任のもとに行われています。

開発の背景
私は、学問的な正解や既存フレームワークの常識よりも、
- 実運用で壊れないこと
- 長期にわたって“使い続けられること”
- 人が本来向き合うべき「設計」に集中できること
その結果として辿り着いたのが、コードではなく「構造」を中心に据えた設計思想です。
Cejunoは、プログラムを書くためのフレームワークではありません。
業務システムの構造を定義し、長期にわたって生かし続けるための基盤です。
リクエストからの実行
まず、ユーザーが会員情報登録ページへアクセスするとURLに該当するファイル(以降、ページファイルと呼びます)であるルート配下の「/member/regist.php」が実行されます(画像①)。
ページファイルでは、セットアップファイルの読み込みを行い、その後EfwSystemのexecuteメソッドを呼び出します(画像②)。
このexecuteメソッドの呼び出し時には、引数として実行パス(以下、内部URI)を指定することができます。
内部URIを省略した場合は、ページファイル自身のパスが使用されますが、異なる内部URIを指定することで、似た動作を別URLで利用したい場合に役立ちます。
例えば、問い合わせフォームのように内部パラメーターの一部が異なるだけで基本動作は同じページを複数用意したい場合、共通となるページの内部URIを渡すことで、コンテンツを一元管理することが可能になります。
Cejuno に込めた思想
- 実装は消耗品である
- 構造は資産である
- 人はコードを書くために存在するのではない
- 技術は人を縛るためではなく、解放するためにある
Workflowコンフィグのロード
PfwSystemは、システムを動作させるための各種APIやリソースをロードした後、内部URIを基に「/system/project/wfc/%内部URI%.xml」(画像③)のWorkflowコンフィグを読み込み、その内容からWorkflowManagerを生成します。
知的財産について
Cejuno(Cejuno Framework、Cejuno IDE、Cejuno CMSの全てを含みます)は、私個人の思想と設計に基づいて構築された知的成果です。
名称・構造・設計思想を含め、私自身の知的財産として明確に位置付けています。
そのうえで、世界に広げ、議論され、検証され、進化していくことを望んでいます。
ソース2-2-1.Workflowコンフィグ
対話・問い合わせについて
Cejunoに関する以下のような内容について、対話を歓迎します。
- 技術的・思想的な質問
- 研究・検証・実証実験
- 協業・共同開発
- 教育・研究機関での活用検討
各種お問い合わせについては、お問い合わせからご連絡ください。
<Workflow>
<ProcessController Type="default" Package="efw.conductor" Class="LoginProcessController">
<Parameter Key="LoginCheckKey" Value="MemberID" />
<Parameter Key="RedirectURL" Value="./index.php" />
</ProcessController>
<Controller Type="project" Package="wfc" Class="ProjectController">
<Model Type="Shopping" Package="order" Class="OrderModel" />
<WorkProcess Name="input">
<Task Type="default" Package="efw.device" Class="EntityGenerator">
<Parameter Key="Class" Value="News" />
</Task>
</WorkProcess>
<WorkProcess Name="confirm">
<Task Type="default" Package="efw.device" Class="EntityGenerator">
<Parameter Key="Class" Value="News" />
</Task>
<Task Type="default" Package="efw.device" Class="EntityValidator">
<Parameter Key="Class" Value="News" />
<Parameter Key="ProcessKey" Value="regist"/>
<Parameter Key="ErrorProcess" Value="ChangeProcess" />
<Parameter Key="ErrorProcessValue" Value="input" />
</Task>
<Task Type="default" Package="efw.device" Class="ImageUploader">
<Parameter Key="Class" Value="News" />
<Parameter Key="Process" Value="Upload" />
<Parameter Key="ErrorProcess" Value="ChangeProcess" />
<Parameter Key="ErrorProcessValue" Value="input" />
</Task>
</WorkProcess>
:
:
</Controller>
<PageViewer Type="project" Package="wfc" Class="ProjectViewer" Layout="">
:
<WorkProcess Name="input" Contents="none">
<Form Category="default" Target="./regist.php" Method="post" FormName="inputForm">
<InputClass Name="News" Title="">
<Input Name="NewsID" Required="true" ViewPattern="hidden" />
:
:
</InputClass>
</Form>
<WebParts Type="Viewer" Class="" Key="printEntityForms">
</WebParts>
</WorkProcess>
<WorkProcess Name="confirm" Contents="none">
:
:
</WorkProcess>
:
:
</PageViewer>
</Workflow>
生成されるWorkflowManagerは、Workflowコンフィグ内のControllerタグを基に生成されます。
Controllerタグを省略した場合は、デフォルトのWorkflowManagerが自動的に生成されます。
また、Workflowコンフィグ自体が存在しない場合は、すべてデフォルトのオブジェクトが生成され、内部URIに対応するHTMLテンプレートがそのままロードされます(画像④)。
WorkProcessの生成
次に、実行対象となるWorkProcessを生成します。どのWorkProcessを実行するかは、内部のProcessContextが保持するリクエストパラメータ「Active Process Name(以降、apnと呼びます)」によって決定されます。
初回アクセス時などでapnが空の場合は、Workflowコンフィグ内で最初に定義されているWorkProcess(このWorkflowではプロセス名:input)が生成され、同時にそのプロセス名がapnへセットされます。
Taskリストの生成と実行
次に、WorkProcess内のTaskリストを生成し、WorkflowManagerは内部のTaskManagerに対して、生成されたTaskを先頭から順に実行させます。
TaskManagerは、あるTaskの実行結果にエラーがない場合は次のTaskへ進みますが、エラーが発生した場合でも「次のTaskを実行してよい」と定義されている場合は、そのまま次のTaskを実行します。
PageViewerの生成とページ表示
Workflowコンフィグをロードした際に、ページを表示するためのPageViewerが生成されます。
PageViewerの設定はWorkflowコンフィグ内に定義されており、その内容を基にインスタンス化されます。
ただし、Workflowの構成によってはPageViewerが不要な場合もあるため、省略することが可能です(省略した場合はデフォルトのPageViewerが生成されます)。
生成されたPageViewerは、「/system/project/config/env.ini」の[Agent Directory]に定義されたユーザーエージェントディレクトリ名、内部URI、そしてPageViewer内のWorkProcess要素に定義されたContents属性(以降、Contents名と呼びます)を組み合わせ、ページ表示用テンプレートファイルのパス(以降、テンプレートパスと呼びます)を生成し、そのページ表示用テンプレート(以降、HTMLテンプレートと呼びます)をロードします。
HTMLテンプレートには表示する内容をHTMLやPHPにてそのまま記述することができますが、PageViewer配下のWebPartsを利用することでHTMLテンプレート自体も省略することができます。その場合、テンプレートファイルはロードされません。
テンプレートパスの基本仕様は以下のとおりです。
内部URI+(Contents属性が空の場合は「_%apn%」→プロセス名が「input」なら「_input」、Contents名が「none」の場合は空、それ以外の場合は「%Contents名%」)+「.inc」
今回の例ではPCからのアクセスを想定しているため、「agent.directory.other="pc"」が選択されます。したがって、内部URI「member/regist」とContents名が空であることから、「/template/pc/member/regist.inc」がテンプレートパスとなります。
また、テンプレートファイルの内容はHTML+PHPで構成されています。これは、他フレームワークに見られる独自テンプレート言語の煩雑さや、面倒さ、不便さ、新たに発生する余計な学習コストを避けるためです。
Cejunoでは、自由度を高め、より簡単かつ柔軟に製造できることを重視しているため、このような仕様を採用しています。
ソース2-5-1.env.ini(Agent Directory部分抜粋)
[Agent Directory]
agent.directory.other="pc"
agent.directory.imode="pc"
agent.directory.ezweb="pc"
agent.directory.softbank="pc"
agent.directory.willcom="pc"
agent.directory.l_mode="pc"
agent.directory.android.mobile="responsive"
agent.directory.android.tablet="responsive"
agent.directory.iphone="responsive"
agent.directory.ipad="responsive"
agent.directory.windowsphone="responsive"
ユーザーエージェントディレクトリ名の選択は、「agent.directory.」に$_SERVER["HTTP_USER_AGENT"]の値を結合したキーが選択されます。
WebPartsの表示
WebPartsを利用することで、HTMLをほとんど記述せずにUIを自動表示することができます。
今回のWorkflowでは、PageViewerがprintEntityFormsメソッドを呼び出しているため、InputClassに記述されたEntityの入力フォームが自動的に生成・表示されます。
WebPartsには多様な使い方があり、さらにWorkflowコンフィグには記述しない「PageParts」と呼ばれるUIパーツも用意されています。
これにより、UI表示に必要なHTMLを大幅に削減でき、柔軟で効率的な画面構築が可能になります。
なお、このドキュメントサイト自体もHTMLを一切記述していませんが、同様にUIパーツを組み合わせることで、このような多彩な画面表示を実現しています。