Asked  7 Months ago    Answers:  5   Viewed   34 times

I'm building a tiny MVC framework for learning/experimenting and small project purposes. I needed to find out the basics of the internals of the Model since a full MVC framework and ORM is overkill for just a few database calls.

Class Model
{
}

Using an empty class where would I have to call a new PDO object for database calls?

What would calling a query look like inside the Model?

Additionally, where can I find a beginner's web/book resource to MVC (with lots of example code)? I've heard a lot of terms such as business logic and database logic. I remember reading somewhere that you should separate business logic and database logic. I can understand the concept somewhat, I just wonder what it looks like or what they mean in code itself. I'm confused how business logic and database logic should be separated but still be inside the Model.

I'm mostly looking for code/logic examples as answers, except maybe the latter paragraph.

 Answers

46

Warning:
The information in this posts is extremely outdated. It represents my understanding of MVC pattern as it was more then 2 years ago. It will be updated when I get round to it. Probably this month (2013.09).

Damn it! (2017.11).

Model itself should not contain any SQL. Ever. It is meant to only contain domain business logic.

The approach i would recommend is to separate the responsibilities, which are not strictly "business logic" into two other other sets of constructs : Domain Objects and Data Mappers.

For example, if you are making a blog, then the Model will not be Post. Instead most likely the model will be Blog , and this model will deal with multiple Domain Objects: multiple instances of Post, Comment, User and maybe other objects.

In your model, the domain objects should not know how to store themselves in database. Or even be aware of the existence of any form of storage. That is a responsibility of Data Mappers. All you should do in the Model is to call $mapper->store( $comment );. And the data mapper should know how to store one specific type of domain objects, and win which table to put the information ( usually the storage of of single domain object actually affects multiple tables ).


Some code

(only relevant fragments from files):

  • I assume that you know how to write a good constructor .. if you have doubts, read this article
  • nothing is namespaced in example, but it should be
  • anything that begins with _ in example is protected

from /application/bootstrap.php

/* --- snip --- */

$connection = new PDO( 'sqlite::memory:' );
$model_factory = new ModelFactory( $connection );

$controller = new SomeController( $request , $model_factory );

/* --- snip --- */

$controller->{$action}();

/* --- snip --- */
  • controller does not need to be aware of database connection.
  • if you want to change DB connection for whole application, you need to change single line
  • to change the way how Model's are made, you create different class which implements same interface as ModelFactory

from /framework/classes/ModelFactory.php

/* --- snip --- */

class ModelFactory implements ModelBuilderInterface
{
   /* --- snip --- */

   protected function _prepare()
   {
      if ( $this->_object_factory === null  )
      {
         $this->_object_factory = new DomainObjectFactory;
      }
      if ( $this->_mapper_factory === null )
      {
         $this->_mapper_factory = new DataMapperFactory( $this->_connection );
      }
   }

   public function build( $name )
   {
      $this->_prepare();
      return new {$name}( $this->_object_mapper , $this->_data_mapper );
   }

   /* --- snip --- */

}
  • only data mappers will use database , only mapper factory need connection
  • all the dependencies of Model are injected in constructor
  • every DataMapper instance in the application uses same DB connection, no Global State (video) required.

file /application/controllers/SomeController.php

/* --- snip --- */

   public function get_foobar()
   {
      $factory = $this->_model_factory;
      $view = $this->_view;

      $foo = $factory->build( 'FooModel' );
      $bar = $factory->build( 'BarModel' );

      $bar->set_language( $this->_request->get('lang') );

      $view->bind( 'ergo' , $foo );

      /* --- snip --- */

   }

/* --- snip --- */
  • controller is unaware of model creation details
  • controller is only responsible for wiring and changing the state of elements

file /application/models/FooModel.php

/* --- snip --- */

   public function find_something( $param  , $filter )
   {
      $something = $this->_object_factory('FooBar');
      $mapper = $this->_mapper_factory('FooMapper');

      $something->set_type( $param );
      $mapper->use_filter( $filter )->fetch( $something );

      return $something;
   }

/* --- snip --- */
  • domain object is responsible for validating the given parameters
  • view receives and decides how to present it
  • mapper takes the object and puts in it all the required information from storage ( it doesn't have to be DB .. it could be taken from some file, or an external REST API )

I hope this will help you understand the separation between DB logic and business logic ( and actually , presentation logic too )


Few notes

Model should never extend Database or ORM, because Model is not a subset of them. By extending a class, you are declaring that has all the characteristics of the superclass, but with minor exceptions.

class Duck extends Bird{}
class ForestDuck extends Duck{}
// this is ok

class Table extends Database{}
class Person extends Table{}
// this is kinda stupid and a bit insulting

Besides the obvious logic-issues, if your Model is tightly coupled with underlaying Database, it makes the code extremely hard to test (talking about Unit Testing (video)).


I personally think, that ORMs are useless and in large project - even harmful. Problem stems from the fact that ORMs are trying to bridge two completely different ways of approaching problems : OOP and SQL.

If you start project with ORM then, after short learning curve, you are able to write simple queries very fast. But by the time you start hitting the ORM's limitations and problems, you are already completely invested in the use of ORM ( maybe even new people were hired , who were really good at your chosen , but sucked at plain SQL ). You end up in situation where every new DB related issue take more and more time to solve. And if you have been using ORM based on ActiveRecord pattern, then the problems directly influence your Models.

Uncle Bob calls this "technical debt".


Few books

loosely related to subject

  • Patterns of Enterprise Application Architecture
  • Agile Software Development, Principles, Patterns, and Practices
  • SQL Antipatterns: Avoiding the Pitfalls of Database Programming
  • PHP Object-Oriented Solutions
  • PHP in Action
Wednesday, March 31, 2021
 
MoarCodePlz
answered 7 Months ago
46

Ok I'm just gonna start by saying I'm no expert in PHP and I recommend using a framework such as symphony to do your routing, but here is one possible solution that you could use.

function regexPath($path)
{
    return '#' . str_replace([":int:", ":string:"], ["d+", ".+"], $path) . '#';
}

function parseRequestedView($url)
{
    $ressource_requested = explode('/', trim($url, '/'));


    // define our routes, and the indices that each route will use (from the exploded url)
    // this could be defined as another parameter or as a member of the class
    $routes = [
        regexPath("article/:int:/comments/show") => [0,  2, 3,  1], // will return array(resource[0], resource[2], resource[3], resource[1]), etc
        regexPath("article/:int:/show")          => [0, -1, 2,  1], // -1 will return a null
        regexPath("archive/show")                => [0, -1, 1, -1]
    ];


    // go through each route, checking to see if we have a match
    foreach ($routes as $regex => $indices)
    {
        if (preg_match($regex, $url))
        {
            // it matched, so go over the index's provided and put that data into our route array to be returned
            foreach ($indices as $index)
            {
                $route[] = $index > -1 ? $ressource_requested[$index] : null;
            }

            // include the post data (not really nessesary)
            $route[] = $_POST; // unnessesary to pass $_POST data through function, because it is global

            return $route;
        }
    }

    return null; // or some default route maybe?
}

$route = parseRequestedView("article/13/comments/show");

echo '<pre>';
print_r($route);
echo '</pre>';

/* returns:
Array
(
    [0] => article
    [1] => comments
    [2] => show
    [3] => 13
    [4] => Array // this is our $_POST data
        (
        )

)
*/
Wednesday, March 31, 2021
 
PLPeeters
answered 7 Months ago
83

I use the Google ReCaptcha and it works very well and is very simple to implement.

Note that if you are using Https be sure you have the current version of the dll (1.0.5.0 at this time)

You need to create an account on the Google Recaptcha site and get a set of public and private keys. Add the keys to your web project main web.config file:

<appSettings>
    <add key="webpages:Version" value="1.0.0.0"/>
    <add key="ClientValidationEnabled" value="true"/>
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
    <add key="ReCaptchaPrivateKey" value="put your private key value here" />
    <add key="ReCaptchaPublicKey" value="put your public key value here" />
</appSettings>

Now use NuGet and install the reCAPTCHA plugin for .NET

Then, go to your web.config file inside of your VIEWS folder. Add this line:

<namespaces>
  <add namespace="System.Web.Mvc" />
  <add namespace="System.Web.Mvc.Ajax" />
  <add namespace="System.Web.Mvc.Html" />
  <add namespace="System.Web.Routing" />
  <add namespace="Recaptcha"/>
</namespaces>

Then, in your view that you want to show the captcha, add the using statement at the top of your file

@using Recaptcha;

then add this to your view:

<div class="editor-label">
    Are you a human?
</div>
<div class="editor-field">
    @Html.Raw(Html.GenerateCaptcha("captcha", "clean"))
    @Html.ValidationMessage("captcha")
</div>

In your controller action you will need to modify the signature to accept the captcha results:

[HttpPost]
[RecaptchaControlMvc.CaptchaValidator]
public ActionResult ForgotPassword(CheckUsernameViewModel model, bool captchaValid, string captchaErrorMessage) {
    if (!Membership.EnablePasswordReset)
        throw new Exception("Password reset is not allowedrn");
    if(ModelState.IsValid) {
        if(captchaValid) {
            return RedirectToAction("AnswerSecurityQuestion", new { username = model.Username });
        }
        ModelState.AddModelError("", captchaErrorMessage);
    }
    return View(model);
}

Following those steps have allowed me to implement captcha on several pages and it works smoothly. Note that the parameter names on the controller action MUST BE NAMED CORRECTLY:

bool captchaValid, string captchaErrorMessage

If you changed these parameter names you WILL get an error at runtime when your form posts back to the controller action.

Thursday, July 22, 2021
 
tpow
answered 3 Months ago
76

The presentation layer contains views and controllers.
You must not mistake an MVC architecture for a multitier/layer architecture (especially a 3-tier architecture). Most of the time Model/View/Controller is not the primary design of a web application, it is just a subset of a multitier/layer architecture.

Take a look at this oversimplified scheme (you could have the DAOs in a dedicated Data Access Layer, but this is not important in this post) :

Simplified layers

Spring MVC is a presentation framework : it deals with controllers and views. But why the "M" in Spring MVC ? Just because, as many other presentation framework, it naturally deals with a representation of a model/entity ("M"). This representation is the one used in your controllers, displayed in your views, submitted in your forms, etc. That's why the framework is called Spring MVC, even if the model/entity is not part of the prensentation layer.

I think it is a good name for this Framework, because it is really "MVC" oriented. Indeed the representation of a model/entity can be :

  • direct : the framework directly handles the model/entity object
  • indirect : the framework handles a form object or DTO, that contains information related to one or multiple entities

Spring's recommendation is to directly use the model/entity ("M") object :

Reusable business code, no need for duplication. Use existing business objects as command or form objects instead of mirroring them to extend a particular framework base class.

That's why I say the framework is very "MVC" oriented, compared to others, like Struts, where you have to use different form objects.

Some interesting links :

  • Comparison between Multitier and MVC architecture, from Wikipedia
  • This blog post about 3-tier architecture in ASP.NET
  • This blog image of a 3-tier architecture
  • DispatcherServlet chapter from Spring's documentation
Monday, August 9, 2021
 
RajaReddy PolamReddy
answered 3 Months ago
35

I think you just do

 Product product = _productRepository.FindProduct(model.ProductId);
 Mapper.Map(model, product);
 _productRepository.SaveChanges();

you may also want to check that you have a non null product first, and also that user is allowed to change that product....

Tuesday, September 21, 2021
 
Krell
answered 1 Month ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share