Please upgrade here. These earlier versions are no longer being updated and have security issues.
HackerOne users: Testing against this community violates our program's Terms of Service and will result in your bounty being denied.

I'd need some clarification on the MVC in Vanilla, please

businessdadbusinessdad Stealth contributor MVP
edited March 2012 in Vanilla 2.0 - 2.8

Hi all,
I'm developing my very first plugin for Vanilla, and I'm fairly confused due to the lack of documentation. My target is (relatively) simple: I need to show a list of Users based on some criteria.

So far I have the Plugin's skeleton working and I'm creating a View that should show the list of Users, and I'm stuck. Here's what I've done and, in bold, my questions.

  • I created a View file.
  • I created a Controller method and called the Render on the View.
  • Now, the View has to contain a form to allow filtering by Year range. I added two fields in the view, Year_From and Year_To.
  • Form should use GET method, as it's a query and it doesn't post any data. Note: To achieve this, I forced Form->Method = 'get' in the controller. Is it the correct approach?
  • Validation: how do I do it? Since the form will be public, I use the Form->IsPostBack() method to figure out when I actually have data. Then I should validate that both Years are Integers greater than a configured value (e.g. 2010), but I can't make it work. I analyzed other plugins, but they all just validate configuration data and use the Configuration Model. In my case, I don't have a Model, since the data will be used to build a query which, in turn, will query some Database Views. There's no Add/Edit/Delete possible. Should I have a Model anyway?
  • Once I receive the correct arguments, where should I perform the Query? As stated previously, I'll need to build an SQL Query which will aggregate data from Database Views. No update operations will be possible, just a "simple" SELECT. I was therefore wondering if I should run the Query in the Controller, or, again, if I should build a Model. In both cases, what is the correct approach to produce a DataSet to feed to the View? I examined some plugins to find an answer, but I've noticed they all tackle the problem differently and, sometimes, in very "dirty" ways.

Apologies for the long post, but I tried to find the answers myself and, for every answer I found, two more questions came out... So I figured out that it would probably be quicker to ask to some experts. :)

Thanks in advance for the help.

Best Answer

  • x00x00 MVP
    edited March 2012 Answer ✓

    Validation help

    outside your class

    if(!function_exists('YearSpan'){
        function YearSpan($Value, $Field, $FormPostedValues){
            $YearTo = ArrayValue('Year_To', $FormPostedValues, 0);
            $YearFrom = ArrayValue('Year_From', $FormPostedValues, 0);
            $Epoch = ArrayValue('Epoch', $FormPostedValues, 0);
            return $YearTo > $YearFrom && $YearTo > $Epoch && $YearFrom > $Epoch;
        }
    }

    inside your post back method.

    $Validation = new Gdn_Validation();
    $FormValues = $Sender->Form->FormValues();
    $Epoch = 2010;
    $Sender->Form->SetFormValue('Epoch',$Epoch);
    $Validation->AddRule('YearRule', 'regex:`^\d{4}$`');
    $Validation->AddRule('YearSpan', 'function:YearSpan');
    $Validation->ApplyRule('Year_From','Required','Year From Required');
    $Validation->ApplyRule('Year_To','Required','Year To Required');
    $Validation->ApplyRule('Year_From', 'YearRule', 'You must enter a valid year for Year From YYYY');
    $Validation->ApplyRule('Year_To', 'YearRule', 'You must enter a valid year for Year To YYYY');
    $Validation->ApplyRule('Year_To', 'YearSpan', 'Year From must be greater than Year To  and both greater than '.$Epoch');
    $Validation->Validate($FormValues);
    $Sender->Form->SetValidationResults($Validation->Results());
    if(!$Sender->Form->ErrorCount()){
        //home free
    }

    another way

    $Validation = new Gdn_Validation();
    ....
    $YearTo = $Sender->Form->GetValue('Year_To');
    $YearFrom = $Sender->Form->GetValue('Year_From');
    $Epoch = 2010;
    if($YearTo > $YearFrom && $YearTo > $Epoch && $YearFrom >  $Epoch){
        $Sender->Form->SetFormValue('ValidYearSpan',TRUE);
    }else{
        $Sender->Form->SetFormValue('ValidYearSpan',FALSE);
    }
    $Validation->ApplyRule('ValidYearSpan', 'Boolean', 'Year From must be greater than Year To and both greater than '.$Epoch);
    ...
    

    grep is your friend.

Answers

  • jspautschjspautsch Themester ✭✭✭

    I'm off to get some sleep, but if you're looking for additional documentation, I'd check out the Vanilla Wiki. They even have a page discussing Forms and Validation.

  • x00x00 MVP
    edited March 2012 Answer ✓

    Validation help

    outside your class

    if(!function_exists('YearSpan'){
        function YearSpan($Value, $Field, $FormPostedValues){
            $YearTo = ArrayValue('Year_To', $FormPostedValues, 0);
            $YearFrom = ArrayValue('Year_From', $FormPostedValues, 0);
            $Epoch = ArrayValue('Epoch', $FormPostedValues, 0);
            return $YearTo > $YearFrom && $YearTo > $Epoch && $YearFrom > $Epoch;
        }
    }

    inside your post back method.

    $Validation = new Gdn_Validation();
    $FormValues = $Sender->Form->FormValues();
    $Epoch = 2010;
    $Sender->Form->SetFormValue('Epoch',$Epoch);
    $Validation->AddRule('YearRule', 'regex:`^\d{4}$`');
    $Validation->AddRule('YearSpan', 'function:YearSpan');
    $Validation->ApplyRule('Year_From','Required','Year From Required');
    $Validation->ApplyRule('Year_To','Required','Year To Required');
    $Validation->ApplyRule('Year_From', 'YearRule', 'You must enter a valid year for Year From YYYY');
    $Validation->ApplyRule('Year_To', 'YearRule', 'You must enter a valid year for Year To YYYY');
    $Validation->ApplyRule('Year_To', 'YearSpan', 'Year From must be greater than Year To  and both greater than '.$Epoch');
    $Validation->Validate($FormValues);
    $Sender->Form->SetValidationResults($Validation->Results());
    if(!$Sender->Form->ErrorCount()){
        //home free
    }

    another way

    $Validation = new Gdn_Validation();
    ....
    $YearTo = $Sender->Form->GetValue('Year_To');
    $YearFrom = $Sender->Form->GetValue('Year_From');
    $Epoch = 2010;
    if($YearTo > $YearFrom && $YearTo > $Epoch && $YearFrom >  $Epoch){
        $Sender->Form->SetFormValue('ValidYearSpan',TRUE);
    }else{
        $Sender->Form->SetFormValue('ValidYearSpan',FALSE);
    }
    $Validation->ApplyRule('ValidYearSpan', 'Boolean', 'Year From must be greater than Year To and both greater than '.$Epoch);
    ...
    

    grep is your friend.

  • x00x00 MVP
    edited March 2012

    I don't really understand what you are trying to do, but anything other than trivial should be in a model of sorts. It just makes thing much easier to understand.

    Model don't necessary have to allow updates. That is the whole point of models, they define the data and operations, even if it is an aggregate/composite you could still create a model. Models query other models, do all sorts of things. There are other models besides database models.

    Controller logic should makes sense, and model provide meaningful methods.

    grep is your friend.

  • businessdadbusinessdad Stealth contributor MVP

    x00 said:
    I don't really understand what you are trying to do, but anything other than trivial should be in a model of sorts. It just makes thing much easier to understand.

    What I'm trying to implement is some sort of statistics report. To make an example, one of the statistics would be a report showing the most active Users by year. User will be presented with a form with a year range, and, by selecting it, he'll be able to see the top X most active users for each year. This can then be extended in searching by month or day, and so on. Of course, this is a very simple example and more complex statistics report would be required, but I'll handle the requests gradually, as they arrive.

    About models, I was thinking of creating one which will return a Dataset based on the arguments I pass it. I guess the base class I should use is Gdn_Model, is it correct?

    Also, once I instantiate the Model and managed to retrieve some data, how do I make it available to the view?

    Thanks again for the help.

  • businessdadbusinessdad Stealth contributor MVP

    x00 said:
    Validation help

    outside your class

    if(!function_exists('YearSpan'){
      function YearSpan($Value, $Field, $FormPostedValues){
          $YearTo = ArrayValue('Year_To', $FormPostedValues, 0);
          $YearFrom = ArrayValue('Year_From', $FormPostedValues, 0);
          $Epoch = ArrayValue('Epoch', $FormPostedValues, 0);
          return $YearTo > $YearFrom && $YearTo > $Epoch && $YearFrom > $Epoch;
      }
    }

    Thank you very much. Out of curiosity, why do I have to declare function YearSpan outside of my class? Is that just to make it globally available?

  • businessdadbusinessdad Stealth contributor MVP

    jspautsch said:
    I'm off to get some sleep, but if you're looking for additional documentation, I'd check out the Vanilla Wiki. They even have a page discussing Forms and Validation.

    Another night owl, I see. Thanks for taking the time to reply, especially so late. I checked the Vanilla Wiki, but I was still confused on how to proceed as the example uses a Model created from a table, whereas I need to create an aggregate query on the fly. I think that x00 suggestions will help me getting on the right track. :)

  • Yep not a fan of that but $Validation->AddRule('YearSpan', 'function: uses function_exists:

    php > class test { public static function foo(){}}
    php > function bar(){}
    php > var_dump(function_exists('test::foo'));
    bool(false)
    php > var_dump(function_exists('bar'));
    bool(true)

    personally I would have used is_callable and call_user_function / call_user_function_array

    php > var_dump(is_callable('test::foo'));
    bool(true)

    grep is your friend.

  • businessdad said:
    What I'm trying to implement is some sort of statistics report. To make an example, one of the statistics would be a report showing the most active Users by year. User will be presented with a form with a year range, and, by selecting it, he'll be able to see the top X most active users for each year. This can then be extended in searching by month or day, and so on. Of course, this is a very simple example and more complex statistics report would be required, but I'll handle the requests gradually, as they arrive.

    About models, I was thinking of creating one which will return a Dataset based on the arguments I pass it. I guess the base class I should use is Gdn_Model, is it correct?

    Also, once I instantiate the Model and managed to retrieve some data, how do I make it available to the view?

    Thanks again for the help.

    Although reports are technically snapshots, it may make sense to store them in some form, so you get a cached result. So I would have a schema. If you use a model, you could then check to see if a report already exists, if not generate and store. Make sense?

    grep is your friend.

  • Anyway you are going to have to record that meta at regular interval like days, then it should be slightly larger interval add and store, and so on. Because otherwise it could be expensive performance wise. I would suggest in you scheme have date, period type (day,week,month,year), and then your meta counts. You need to sort out a performance effective cron, based user request. Day is easy, you make sure there is a record today for that person so the date need do be greater than the start of the day.

    grep is your friend.

  • businessdadbusinessdad Stealth contributor MVP

    x00 said:

    Although reports are technically snapshots, it may make sense to store them in some form, so you get a cached result. So I would have a schema. If you use a model, you could then check to see if a report already exists, if not generate and store. Make sense?

    That's for sure, it will be done in phase two; at the moment I just would like to be able to show something. :)
    I'm now "playing" with the model, I'll just have to find the proper way to pass it to the View. Unfortunately, "learning by copying" is one a very inefficient method for someone like me, who doesn't do anything if he doesn't fully understand why it's done like that.

    x00 said:
    Anyway you are going to have to record that meta at regular interval like days, then it should be slightly larger interval add and store, and so on. Because otherwise it could be expensive performance wise.

    I know, databases are actually my specialty. :) However, even without the caching, with appropriate indexes the query will be quite fast. I'll start thinking about a cron task, though, as soon as I find out how to implement it. :)

  • x00x00 MVP
    edited March 2012

    businessdad said:
    I know, databases are actually my specialty. :) However, even without the caching, with appropriate indexes the query will be quite fast. I'll start thinking about a cron task, though, as soon as I find out how to implement it. :)

    Sure, it depends on circumstances and how you are adding up.

    If you look at my KarmaBank plugin there is a simple cron based on user meta. You can't always rely on hooks.

    grep is your friend.

Sign In or Register to comment.