Ankündigung

Einklappen
Keine Ankündigung bisher.

Eine Rule-Engine erstellen

Einklappen

Neue Werbung 2019

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • Eine Rule-Engine erstellen

    Hallo,

    ich brauchte für ein aktuelles Projekt eine Regelverarbeitung. Die Regeln sollten menschenlesbar gestaltet sein. Meinen Ansatz und Umsetzung wollte ich hier kurz vorstellen mit der Bitte mir zu zeigen ob ich das so richtig gemacht habe, oder wie man da sonst rangeht. Ich will einfach lernen

    Also es ging darum voneinander abhängige Kategorien gegen einen Regelsatz zu prüfen. Die Kategorienhirarchie sieht exemplarisch so aus:

    Cat => Cat1
    Subcat => Cat2
    SubSubCat => Cat3
    SubSubSubCat => Cat4
    SubSubSubSubCat => Cat5

    Also fünfstufig (derzeit). Das was hier als "Cat" steht ist einmal eine ID ("kategorieid1") und einmal ein Label ("Meine Hauptkategorie"). In den zu prüfenden Objekten sind die IDs enthalten, der Mensch sieht im WebGui die Label und formuliert auch damit die Regeln.
    Die gewählten Kategorien werden ihrer Reihenfolge nach in einen String abgelegt, durch '/' getrennt, also z.B. "/Label Cat1/Label Cat2/Label Cat3". Der "Pfad" enthält nur die gewählten Kategorien (sind alle optional).
    Das Regelwerk soll in der Lage sein auf bestimmte Kategorielabel zu machten, oder diese durch einen Wildcard ('*') zu ersetzen. Dabei sollen die Regeln von vorn nach hinten durchgearbeitet werden (first rule matches). In der Regeldatei wird dann noch eine auszuführende Aktion hinterlegt, wenn die Regel zutrifft. Das ist hier aber nicht von Belang.

    Hier ein paar Beispiele für Regeln und was sie matchen sollen:
    "/Label Cat1/*" => soll auf alle Objekte zutreffen, die "Label Cat1" als Hauptkategorie eingestellt haben und jede weitere darunter beliebig ist. Wichtig: Es muss wenigstens die nachfolgende Kategorie auch gewählt sein!
    "/Label Cat1/Label Cat2" => hier soll nur das Objekt gewählt werden was exakt diese beiden Kategorien ausgewählt hat, keine mehr.
    "/Label Cat1/*/Label Cat3" => Die zweite gewählte Kategorie ist beliebig.
    "/Label Cat1/*/Label Cat3/*" => Die zweite gewählte Kategorie ist beliebig sowie ab der vierten, es muss aber wenigstens eine vierte gewählt sein.
    "/*" triff auf alle Kategorien zu (Fallback/Default).

    Die Anforderung und geplante Umsetzung dürfte jetzt nicht so überraschend und ungewöhnlich sein

    Ich übersetze die Regeldatei mit folgendem Code in ein Array:

    PHP-Code:
                    // load mapping from file
                    
    $file $RUNTIME['maps']['portalredir_file'];
                    if ( ! 
    is_file($file)) {
                            throw new 
    Exception("Portalredir map not found: ".$file10);
                    }
                    
    $txt = @file_get_contents($file);
                    if ( ! 
    $txt) {
                            throw new 
    Exception("Portalredir map is empty: ".$file10);
                    }

                    
    // convert to array and resolve names to IDs
                    
    $map = array();
                    
    $txt explode("\n"$txt);
                    foreach (
    $txt as $line)
                    {
                            
    $line trim($line);
                            if (
    $line == '') continue; // ignore empty lines
                            
    if (substr($line,0,1) == '#') continue; // ignore comments
                            
    $s explode('=>'$line);
                            if (
    count($s) != 2) {
                                    throw new 
    Exception("Bad characters or missing delimiter in portalredir map: ".$file10);
                            }
                            
    $name trim($s[0]);
                            
    $portal trim($s[1]);

                            
    $names explode('/'substr($name1));
                            
    $ids $ecms->categoryIds($names$chan); // Return category ID from label text
                            
    $ids $ids $names;
                            
    # $id = '/' . implode('/', $ids);
                            
    $rule = array(
                                    
    'match' => $ids,
                                    
    'target' => $portal,
                            );
                            
    $map[] = $rule;
                    } 
    Anschließend habe ich in $map eine solche Struktur:
    Code:
    Array (
      0 => array(
        'match' => array(
           0 => 'catid1',
           1 => '*',
           2 => 'catid3',
        ),
        'target' => "...."
      ),
      1 => array(
      ...
    )
    Nun kommt ein Aufruf von einem zu prüfenden Objekt mit einem bereits als IDs vorliegenden Kategoriestruktur, also
    Code:
    Array (
      0 => 'catid1',
      1 => 'catid2',
      2 => 'catid3',
    )
    Mit folgender Iteration prüfe ich dann:

    PHP-Code:
            // check for matching rule
            
    $jobcat_ids explode('/'substr($jobinfo['category'],1));
            
    $jlast count($jobcat_ids) - 1;
            
    $portal '';
            foreach (
    $map as $rule)
            {
                    
    // get index of last element of rule
                    
    $rlast count($rule['match']) - 1;

                    
    // if number of elements in jobcats and current rule differ,
                    // and last element of rule is not a wildcard ('*'), it is no match!
                    
    if ($jlast != $rlast && $rule['match'][$rlast] != '*') {
                            continue;
                    }

                    
    // if there are more jobcat-elements than rule-elements, and
                    // last rule-element is wildcard ('*') replace it with slice of jobcat-elements
                    
    if ($jlast $rlast && $rule['match'][$rlast] == '*') {
                            
    $rule['match'] = array_slice($rule['match'], 0$rlast 1) + array_slice($jobcat_ids$rlast);
                    }

                    
    // now, replace all inner wildcards
                    
    foreach ($rule['match'] as $i => $part)
                    {
                            if (
    $part == '*') {
                                    
    $rule['match'][$i] = $jobcat_ids[$i];
                            }
                    }

                    
    // at least compare rule with jobcat for match
                    
    if ($rule['match'] == $jobcat_ids) {
                            
    $portal $rule['target'];
                            break;
                    }
            } 
    Am Schluß soll $portal entweder leer sein (keine Regel traf zu) oder einen String enthalten.
    Die Idee hinter diesem Code war folgender:
    1. Es wird Regel für Regel gegen die im Objekt vorliegende Kategoriestruktur geprüft
    2. Wenn die Anzahl der Elemente von Objekt-Kategorie und Regel-Teilen unterschiedlich ist und am Ende der Regel kein '*' steht, kann es kein Treffer sein
    3. Gibt es mehr Elemente in der Objekt-Kategorie als in der Regel und das letzte Regel-Element ist ein '*', dann ersetze den Teil der Regel ab dem Stern mit den Werten des Objekt-Kategorie ab dieser Position
    4. Ersetze ggf. vorhandene Wildcards ('*') in der Regel mit dem an gleicher Position vorkommenden Kategorie des Objekts
    5. Vergleiche den so modifizierte Regelsatz mit der aktuellen Objektkategorie. Sind sie gleich, trifft die Regel zu und die Iteration wird beendet.

    Ich bin über mehrere Ansätze dort hin gekommen, aber das war für mich der effektivste Code. Womöglich ist das ja alles totaler Unfug und jemand mit höherer Schulbildung oder Wissenstand kann mir erklären wie man solche Probleme richtig löst?!
    Sage schonmal danke fürs lesen und mitdenken!

  • #2
    Ich hab mir Deinen Code nicht angeschaut, aber wenn Du wirklich das willst was eine "Rule-Engine" ist dann schau Dir doch einfach einer der zahlreichen fertigen Implementierungen an die $suchmaschine dazu liefert. Dort kannst Du Dir definitiv Anregungen holen oder gleich eine Fertige verwenden.

    Kommentar

    Lädt...
    X