PHP再帰-アルゴリズム、アプリケーション

階層リストを作成する分野での熟考と実験の時間は、記事の執筆を促しました。 当初、ロジックはSQLクエリで実行されていましたが、DBMSへの依存を取り除くために、後でPHPに実装することにしました。 簡単な例を使用して、階層のルートから各有限要素に、またはその逆にどのように進むことができるかを示します。初心者にとっては情報のほうが多くなります。



それで、私たちが作業しなければならないテスト階層:



画像



データベースには、最も単純なMSSQLサーバー上に最も単純なテーブルがあります。接続の微妙な部分は省略し、階層と再帰に対処することを目標としています。



テーブルを作成します。



CREATE TABLE [dbo].[Test]( [uid] [int] IDENTITY(1,1) NOT NULL, --  ,  [pid] [int] NULL, --       ,  uid  [name] [varchar](255) NULL, [access] [varchar](50) NULL, --   ) ON [PRIMARY]
      
      





詳細を入力します。



画像



フィールドの説明はコメントにあり、 アクセスフィールドについてもう少し詳しく説明します



デフォルトでは、私のシステムでは、新しいドキュメントごとに継承が設定されます。つまり、親からの継承です。 この実験では、いくつかの要素のドメイングループを作成します。 Domain Usersグループでは、私のアカウントは利用可能ですが、 AD Group Secretでは利用できません。



他に何がありますか。 ドメイングループのリストを含む配列。 IISでWindows認証が有効になり、すべてが透過的に機能します。PHPでは、ログインのログインは変数$ _SERVER ["AUTH_USER"]にあり、LDAPリクエストを含むグループのリストを取得します。



今、私は必要なデータを取得して直接ビジネスに行くことを提案します:



 $stmt = $PDO->query("SELECT * FROM Test"); $table = $stmt->fetchAll(); //    $groups = LDAP::getGroups('$login'); //  ActiveDirectory
      
      





タスク番号1



リストではなくツリーのような階層を操作する方法を学ぶ必要があります。 ネストのレベルは事前にわかっておらず、どのレベルでもかまいません。そのため、ツリーを上から下、反対方向に通過できる汎用ツールが必要です。



タスク番号2



アクセスを柔軟に制御する必要があります。つまり、グループ、個々のドキュメントなどに権限を与えるには、NTFSファイルシステムと同様に、フォルダ全体の権限を閉じることができますが、このフォルダ内の1つのドキュメントについては、アクセスを切ります-同じことが起こるはずです私たちと。



タスク番号3



ユーザーがアクセスできないリソースをユーザーから隠す必要がありますが、最も重要なことは、ブランチの深さのどこかに少なくとも1つのドキュメントに対する権限がある場合、このドキュメントにつながる要素を表示することです(そうでなければ、ユーザーはどのようにアクセスしますか?)



基本機能自体は次のとおりです。



 $array = array(); //  function recursive($data, $pid = 0, $level = 0){ global $array; foreach ($data as $row) { //  if ($row['pid'] == $pid) { //  , pid    ,    0, ..   //     $_row['uid'] = $row['uid']; $_row['pid'] = $row['pid']; $_row['name'] = $_row['name'] = str_pad('', $level*3, '.').$row['name']; // str_pad   $_row['level'] = $level; //  $array[] = $_row; //      // ,        uid,   //    (   uid  pid-) recursive($data, $row['uid'], $level + 1); } } } recursive($table); //
      
      





ほとんどの場合、説明はコメントで引用されていますが、簡単に言うと、foreachループが行を渡してデータを処理した後(この場合、データを別の配列にコピーし、名前フィールドにレベルフィールドとドットを追加して)、同じように開始します関数、それに文字列のuidを渡します。if条件ではpidと比較するため、次の実行では子要素が明確にキャプチャされます。 foreachループは、親のuidが渡された値と一致するすべての行を反復処理するため、それ自体を再起動すると、関数は各レベルの各要素で機能します。 明確にするために、レベルを1つ上げて合格します。 その結果、どのドキュメントがどのレベルのネストを持っているかがわかります。



ブラウザに$配列を出力します。



画像



すでに悪くないですよね?



それでは、関数を少し複雑にしてみましょう。



 $array = array(); //  $array_idx_lvl = array(); //   level function recursive($data, $pid = 0, $level = 0, $path = "", $access_parent = "inherit"){ global $array; global $array_idx_lvl; //  level global $groups; //  //  foreach ($data as $row) { //  , pid    ,    0, ..   if ($row['pid'] == $pid) { //     $_row['uid'] = $row['uid']; $_row['pid'] = $row['pid']; $_row['name'] = str_pad('', $level*3, '.').$row['name']; $_row['level'] = $level; //  $_row['path'] = $path."/".$row['name']; //    $_row['view'] = ""; //  if($row['access'] == 'inherit') { $_row['access'] = $access_parent; //  ,     } else { $_row['access'] = (in_array($row['access'], $groups)) ? 'allow' : 'deny'; } $array[$row['uid']] = $_row; //    uid //    level,   $array_idx_lvl[$level][$row['uid']] = $row['uid']; // ,        uid,   //    (   uid  pid-) recursive($data, $row['uid'], $level + 1, $_row['path'], $_row['access']); } } } recursive($table); //
      
      





順番に解析:



1. パスフィールドが追加されました-パスを形成するには、文字列名を「/」値に追加し、結果の値を関数に渡します。関数は履歴が繰り返され、ルートから要素へのパスが出力されます。



2.結果の配列は、スクラッチから始まりますが、uidにバインドされた順不同で形成されます- $ array [$ row ['uid']] = $ _row; 。 この場合、これはスクリプト操作にはまったく影響しませんが、ツリーの通過を逆方向に解析するときに、将来ループを反復するのではなく、インデックスで行にアクセスする機能が必要になります。



3.インデックスの追加$ array_idx_lvl = array(); 。 後でこのインデックスも必要になります。意味は次のとおりです。結果セットは1つのヒープにスタックされず、レベルごとにインデックス付けされた配列に分割されます。



4. アクセスフィールド。 関数が残りのパラメーターと共に自分自身を起動すると、 $ _row ['access']許可設定がchildrenに渡され 、次が発生し、権限がチェックされます-継承が設定されている場合は親の権限が適用され、そうでない場合はin_arrayを確認しますログインしているユーザーのグループの中に、アクセスで指定されたドメイングループがあるかどうか。 存在する場合は、行に許可を追加し、そうでない場合は拒否します。



最終結果:

画像



さて、降下を把握しましたが、要素の可視性を決定する最後のビューフィールドの上昇と塗りつぶしを処理するために残っています。 記事の冒頭で、なぜこれが必要なのかを説明しましたが、別の状況が想定されます。 ツリービューをサイトのナビゲーションメニューにバインドし、多数のアイテムを含むマルチレベルのドロップダウンリストの形式で作成するとします。1つのドキュメントのみにアクセスするユーザーがこの配列全体を表示して大量のメニューでそのアイテムを探すことは望ましくありません実際、彼は目的のボタンにつながるブランチを1つだけ表示する必要があります。



なぜ反対方向に通路があるのですか? ユーザーが、最も遠い(最終レベルの)ドキュメントを除くすべてのコンテンツにアクセスできるとします。考えてみると、アクセス可能なドキュメントから始めて、必要な要素のみを表示してツリーのルートに導くのが論理的です。



続行:



 //          uid,  //            ... function backRecursive($uid, $view = null, $ident = 0) { global $array; //        if($ident <= 1) { //    -    ,  //        if($array[$uid]['view'] != 'show') { $array[$uid]['view'] = ($array[$uid]['access'] == 'allow' or $view == 'show') ? 'show' : 'hide'; } backRecursive($array[$uid]['pid'], $array[$uid]['view'], $ident+1); } }
      
      





この関数は、演技を開始する行のuidパラメーターを取得し、この行にアクセスして可視性を確認します。 ビューフィールドが表示されない(つまり表示)が、何か他のものが安全であることを確認し、 許可がある場合 (アクセスが開いている)、要素を表示し、そうでない場合は非表示( 非表示 )にしてから、それ自体を起動しますpidと可視性の設定を渡すだけでなく、 $ ident変数を1増やして、以降のセルフスタートをブロックします。 2回目のパスでは、親要素は渡されたpidに配置されますが、1つを除いて同じチェックが実行されます。ただし、 $ view変数で子から ' show 'が渡された場合、現在の要素にもshowが割り当てられます (つまり、表示されます)。



私の意見では、リミッターを使用することが最良の選択肢です。レベル10では100のドキュメントがあり、ツリー全体を完全に走査するには、各要素でこの関数を実行する必要があるため、 最後のレベルで関数を100回実行してからセルフスタートを実行すると、ルートに対して100回反復されます。 10レベルを掛けると、すでに1000サイクルになりますが、これは良くないので、レベルごとに均等に上昇させる必要があります。



次のコードは、この関数を起動します。



 function startBack(){ global $array_idx_lvl; $levels = array_keys($array_idx_lvl); //   $maxLevel = max($levels); //     //         for ($i = $maxLevel; $i > 0; $i--) { $uids = array_keys($array_idx_lvl[$i]); //              1 foreach ($uids as $uid) { backRecursive($uid); } } }
      
      





これは、レベルインデックスが必要だった場所です。 ここでは、最も遠いレベルから移動し、それぞれに進み、その中のすべての要素を処理します。



そして、ここに写真があります:



画像



開始する前に、コードが正しく機能することを明確に示すために、項目「税のレポート」に許容グループを意図的に指定しました。 「経理」セクションへのアクセスは閉じられているにもかかわらず、表示されています。



それだけです、私たちはタスクに対処したと思います、基礎が受け取られ、アルゴリズムが機能し、実際のシステムに適用できます。



All Articles