Assume that you are going to develop a MVC framework from scratch. This framework should allow further extension by somebody else. Below is my simple approach. I aim to create a simple structure with some simple scripts/ classes as a small part of a much bigger project. This simple structure imposes no restrictions on where any given class is located - as long as the autoloader can autoload the class. You can add more folders or tear apart them as you like.
Directory structure,
conf/
app/
public/
vendor/
test/
index.php
inside app/
, in which you can populate your modules, and contains an example module - contact/
,
app/
adapter/PdoAdapter.php (database connection)
controller/contact/
mapper/contact/
model/contact/
client/contact/
service/contact/
strategy/
view/contact/
Inside controller/contact
, contains ContactController.php
,
use Foo\Strategy\ControllerStrategy;
class ContactController implements ControllerStrategy
{
public function setService( \Foo\Strategy\ServiceStrategy $ServiceStrategy )
{
$this->ServiceStrategy = $ServiceStrategy;
return $this;
}
public function fetchContact( $options = [] )
{
$this->ServiceStrategy->fetchContact( $options );
}
public function fetchContacts( $options = [] )
{
$this->ServiceStrategy->fetchContacts( $options );
}
public function updateContact( $options = [] )
{
$this->ServiceStrategy->updateContact( $options );
}
public function deleteContact( $options = [] )
{
$this->ServiceStrategy->deleteContact( $options );
}
}
Inside model/contact
, contains ContactModel.php
in ContactModel.php
,
namespace Foo\Model;
use Foo\Strategy\ModelStrategy;
class RetrieveContactModel implements ModelStrategy
{
public $contact_id;
public $name;
public $phone;
public $street;
public $error;
/*
* Get contact name or other manipulations that only concerns itself only.
*/
public function getName()
{
return ucwords($this->name);
}
// And other methods...
}
Inside view/contact
, contains ContactView.php
,
namespace Foo\View;
use Foo\Strategy\ViewStrategy;
class RetrieveContactView implements ViewStrategy
{
/*
* Set props.
*/
private $contact;
private $format;
/*
* Contruct the dependency.
*/
function __construct( \Foo\Strategy\ModelStrategy $ModelStrategy, $format = null )
{
$this->contact = $ModelStrategy;
$this->format = $format;
}
/*
* Render the page with data.
*/
function render()
{
switch( $this->format )
{
case "json":
// Set header.
header('Content-Type: application/json');
if( !empty( $this->contact->error) )
{
// Unset the empty properties.
unset($this->contact->contact_id);
unset($this->contact->name);
unset($this->contact->phone);
unset($this->contact->street);
// Encode the data into json.
echo json_encode( $this->contact );
break;
}
// Unset the empty properties, in this case - $error.
unset($this->contact->error);
// Encode the data into json.
echo json_encode( $this->contact );
break;
case "xml":
// Set header.
header('Content-Type: application/xml');
// Format the data into xml...
break;
default:
// Turn on output buffering
ob_start();
// Store the frogs model in the $frog handler.
$contact = $this->contact;
// Get the main template/ html document
include WEBSITE_DOCROOT . 'public/template/page/contact/index.php' ;
// Get current buffer contents and delete current output buffer
$html = ob_get_clean();
// Return the content.
return $html;
}
}
}
Inside mapper/contact
, contains ContactMapper.php
,
namespace Foo\Mapper;
use Foo\Strategy\PopulateStrategy;
class ReadContactMapper implements PopulateStrategy
{
/*
* Set props.
*/
protected $PdoAdapter;
/*
* Construct dependency.
*/
public function __construct( \Foo\Adapter\PdoAdapter $PdoAdapter )
{
$this->PdoAdapter = $PdoAdapter;
}
/*
* Map the data to model.
*/
public function populate( \Foo\Strategy\ModelStrategy $ModelStrategy, $options = [] )
{
// Prepare the SQL.
$sql = "
SELECT *
FROM contact AS c
WHERE c.contact_id = ?
";
/// Store the result.
$result = $this->PdoAdapter->fetchRow( $sql, array(
$options['contact_id']
) );
$ModelStrategy->contact_id = $result['contact_id'];
$ModelStrategy->name = $result['name'];
$ModelStrategy->phone = $result['phone'];
$ModelStrategy->street = $result['street'];
}
}
Inside service/contact
, contains ContactService.php
,
namespace Foo\Service;
use Foo\Strategy\ServiceStrategy;
class ContactService implements ServiceStrategy
{
public function setMapper( \Foo\Strategy\PopulateStrategy $PopulateStrategy )
{
$this->PopulateStrategy = $PopulateStrategy;
return $this;
}
public function setModel( \Foo\Strategy\ModelStrategy $ModelStrategy )
{
$this->ModelStrategy = $ModelStrategy;
return $this;
}
public function fetchContact( $options = [] )
{
$this->PopulateStrategy->populate( $this->ModelStrategy, $options );
// Return the populated model to the requestor.
return $this->ModelStrategy;
}
public function fetchContacts( $options = [] )
{
$this->PopulateStrategy->populate( $this->ModelStrategy, $options );
// Return the populated model to the requestor.
return $this->ModelStrategy;
}
public function updateContact( $options = [] )
{
$this->PopulateStrategy->populate( $this->ModelStrategy, $options );
// Return the populated model to the requestor.
return $this->ModelStrategy;
}
public function deleteContact( $options = [] )
{
$this->PopulateStrategy->populate( $this->ModelStrategy, $options );
// Return the populated model to the requestor.
return $this->ModelStrategy;
}
}
Inside strategy/
, contains a few interface classes, such as, ControllerStrategy.php
,
namespace Foo\Strategy;
interface ControllerStrategy
{
public function setService( \Foo\Strategy\ServiceStrategy $ServiceStrategy );
}
ServiceStrategy.php
,
namespace Foo\Strategy;
interface ServiceStrategy
{
public function setMapper( \Foo\Strategy\PopulateStrategy $PopulateStrategy );
public function setModel( \Foo\Strategy\ModelStrategy $ModelStrategy );
}
and so onâŚ
Inside client/contact
, contains index.php
, this is where how all the classes above being wired up and glued together,
// Composer autoloader.
require '../../vendor/autoload.php';
/*============================================================
MAKE DB CONNECTION
============================================================*/
// Instance of PdoAdapter.
$PdoAdapter = new \Foo\Adapter\PdoAdapter( DSN, DB_USER, DB_PASS );
// Make connection.
$PdoAdapter->connect();
/*============================================================
PUT PIECES INTO A MODULE
============================================================*/
// Mapper.
use Foo\Mapper\ReadContactMapper;
// Model.;
use Foo\Model\RetrieveContactModel;
// Service.
use Foo\Service\ContactService;
// Controller.
use Foo\Controller\ContactController;
// View.
use Foo\View\RetrieveContactView;
// Declare the re-usealbe service and controller.
$ContactService = new ContactService();
$ContactController = new ContactController();
// Prepare contacts model.
$ContactModel = new RetrieveContactModel( $format );
$ContactMapper = new ReadContactMapper( $PdoAdapter );
// Inject mapper & model.
$ContactService->setMapper( $ContactMapper )->setModel( $ContactModel );
$ContactController->setService( $ContactService )->fetchContact( array(
"contact_id" => $id
) );
// Prepare the view.
$RetrieveContactView = new RetrieveContactView( $ContactModel, $format );
// Render the view.
echo $RetrieveContactView->render();
This proposal is very different from the popular PHP frameworks that you might be working on, but this is how I understand what MVC is.
My only question is - am I violating any design patterns and principles (such as SOLID)?