# **Chapter 12: Integrating Third-Party Database Models with SartajPHP**
> **Prerequisites:** Chapters 1-11 cover basic SartajPHP concepts and Page helper methods
---
## **12.1 Why Integrate Third-Party Database Models?**
While SartajPHP's built-in `$this->page` helper methods (`viewData`, `insertData`, `updateData`, `deleteRec`) work well for simple cases, you may need:
- **Advanced ORM features** - Relationships, migrations, query builders
- **Multiple database support** - Connect to different databases simultaneously
- **Code reuse** - Use existing Doctrine/ELOQUENT models in SartajPHP
- **Complex queries** - Join builders, aggregations, pagination
This chapter shows how to integrate Doctrine ORM with SartajPHP while maintaining the component system.
---
## **12.2 Understanding the Page Helper Methods**
Before integrating third-party models, let's understand how SartajPHP's built-in methods work:
### **How viewData() Works**
```php
public function viewData($form, $recID = "", $fldList = "", $where = "")
{
// 1. Get all children components from form
$compList = $form->getAllChildren();
// 2. Filter components with dfield attribute
foreach ($compList as $compid => $comp) {
if ($comp->dfield != '' && !$comp->blnDontFill) {
// Store table and field info
}
}
// 3. Build and execute SELECT query
// 4. Set component values from database row
}
```
### **Component Database Binding Flags**
- `dfield`: Database field name
- `dtable`: Database table name (optional, defaults to `$this->page->tblName`)
- `blnDontFill`: Skip filling from database (set via `fur-unsetDataFill="true"`)
---
## **12.3 Creating a Model Wrapper**
Create a wrapper class that bridges Doctrine entities with SartajPHP components:
```php
<?php
// apps/models/EntityModel.php
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\Mapping\AnnotationDriver;
use Doctrine\Common\Annotations\AnnotationReader;
class EntityModel
{
private static $entityManager = null;
private static $paths = array();
private static $isDevMode = true;
/**
* Initialize Doctrine EntityManager
*/
public static function init($paths, $dbParams, $isDevMode = true)
{
self::$paths = $paths;
self::$isDevMode = $isDevMode;
$config = Setup::createAnnotationMetadataConfiguration(
$paths,
$isDevMode
);
self::$entityManager = EntityManager::create($dbParams, $config);
}
/**
* Get EntityManager instance
*/
public static function getEM()
{
return self::$entityManager;
}
/**
* Find entity by ID
*/
public static function find($entityName, $id)
{
return self::$entityManager->find($entityName, $id);
}
/**
* Find all entities
*/
public static function findAll($entityName)
{
return self::$entityManager->getRepository($entityName)->findAll();
}
/**
* Custom query
*/
public static function query($entityName, $dql, $params = array())
{
return self::$entityManager->createQuery($dql)
->setParameters($params)
->getResult();
}
/**
* Save entity (insert or update)
*/
public static function save($entity)
{
self::$entityManager->persist($entity);
self::$entityManager->flush();
return $entity;
}
/**
* Delete entity
*/
public static function delete($entity)
{
self::$entityManager->remove($entity);
self::$entityManager->flush();
}
}
```
---
## **12.4 Component-to-Entity Mapper**
Create a mapper that fills SartajPHP components from Doctrine entities:
```php
<?php
// apps/models/ComponentMapper.php
class ComponentMapper
{
/**
* Fill form components from Doctrine entity
*/
public static function fillFromEntity($formComponent, $entity, $mapping = array())
{
$compList = $formComponent->getAllChildren();
foreach ($compList as $compid => $comp) {
// Check if component has explicit mapping
if (isset($mapping[$compid])) {
$method = 'get' . ucfirst($mapping[$compid]);
if (method_exists($entity, $method)) {
$comp->fi_setDefaultValue($entity->$method());
}
continue;
}
// Auto-map: dfield -> entity property
if (!empty($comp->dfield) && !$comp->blnDontFill) {
$getter = 'get' . ucfirst($comp->dfield);
if (method_exists($entity, $getter)) {
$value = $entity->$getter();
$comp->fi_setDefaultValue($value);
}
}
}
// Set record ID
$idGetter = 'get' . ucfirst($comp->dtable ?? 'id');
if (method_exists($entity, $idGetter)) {
$formComponent->setRecID($entity->$idGetter());
}
}
/**
* Extract component values to array (for Doctrine entity)
*/
public static function extractToArray($formComponent, $mapping = array())
{
$data = array();
$compList = $formComponent->getAllChildren();
foreach ($compList as $compid => $comp) {
if ($comp->blnDontSubmit) continue;
// Use explicit mapping or dfield
$fieldName = $mapping[$compid] ?? $comp->dfield ?? null;
if ($fieldName && !empty($comp->value)) {
$data[$fieldName] = $comp->value;
}
}
return $data;
}
/**
* Create and populate Doctrine entity from form
*/
public static function createFromForm($formComponent, $entityClass, $mapping = array())
{
$data = self::extractToArray($formComponent, $mapping);
// Create entity instance
$reflect = new ReflectionClass($entityClass);
$entity = $reflect->newInstanceWithoutConstructor();
// Set properties
foreach ($data as $field => $value) {
$setter = 'set' . ucfirst($field);
if (method_exists($entity, $setter)) {
$entity->$setter($value);
}
}
return $entity;
}
}
```
---
## **12.5 Complete Integration Example**
### **Step 1: Define Doctrine Entity**
```php
<?php
// apps/models/Entities/Product.php
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="products")
*/
class Product
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $name;
/**
* @ORM\Column(type="decimal", scale=2)
*/
private $price;
/**
* @ORM\Column(type="text", nullable=true)
*/
private $description;
/**
* @ORM\Column(type="string", length=100)
*/
private $category;
/**
* @ORM\Column(type="datetime")
*/
private $createdAt;
// Getters and Setters
public function getId() { return $this->id; }
public function getName() { return $this->name; }
public function setName($name) { $this->name = $name; return $this; }
public function getPrice() { return $this->price; }
public function setPrice($price) { $this->price = $price; return $this; }
public function getDescription() { return $this->description; }
public function setDescription($desc) { $this->description = $desc; return $this; }
public function getCategory() { return $this->category; }
public function setCategory($cat) { $this->category = $cat; return $this; }
public function getCreatedAt() { return $this->createdAt; }
public function setCreatedAt($date) { $this->createdAt = $date; return $this; }
}
```
### **Step 2: Initialize in prerun.php**
```php
<?php
// prerun.php
// Initialize Doctrine
$paths = array(__DIR__ . "/apps/models/Entities");
$dbParams = array(
'driver' => 'pdo_mysql',
'host' => 'localhost',
'dbname' => 'db1',
'user' => 'root',
'password' => 'usbw'
);
EntityModel::init($paths, $dbParams, true);
```
### **Step 3: Create Gate with Doctrine Integration**
```php
<?php
// apps/Product.gate.php
use Sphp\tools\BasicGate;
class Product extends BasicGate
{
private $frtList = null;
private $frtEdit = null;
// Entity to field mapping (handles naming differences)
private $fieldMapping = array(
'txtName' => 'name',
'txtPrice' => 'price',
'txaDescription' => 'description',
'sltCategory' => 'category'
);
public function onstart()
{
$this->frtList = new FrontFile($this->mypath . "/fronts/product_list.front");
$this->frtEdit = new FrontFile($this->mypath . "/fronts/product_edit.front");
}
/**
* URL: product.html - List products
*/
public function page_new()
{
// Using Doctrine instead of dbEngine
$products = EntityModel::findAll('Product');
$html = "";
foreach ($products as $product) {
$html .= "<div class='product'>";
$html .= "<h3>{$product->getName()}</h3>";
$html .= "<p>Price: \$ {$product->getPrice()}</p>";
// Use dynamic URL
$editUrl = getThisGateURL('edit', $product->getId());
$html .= "<a href='{$editUrl}'>Edit</a>";
$html .= "</div>";
}
$this->frtList->getComponent("divProducts")->setInnerHTML($html);
// Add create button URL
$createUrl = getThisGateURL('create');
$this->frtList->addProp("createUrl", $createUrl);
$this->setFrontFile($this->frtList);
}
/**
* URL: product-create.html - Show create form
*/
public function page_event_create($evtp)
{
$this->frtEdit->getComponent("txtId")->fi_setDefaultValue("");
$this->frtEdit->addProp("formTitle", "Add New Product");
$this->frtEdit->addProp("isEditMode", false);
// Set form action dynamically
$submitUrl = getThisGateURL('save');
$this->frtEdit->addProp("formAction", $submitUrl);
$this->setFrontFile($this->frtEdit);
}
/**
* URL: product-edit-5.html - Show edit form
*/
public function page_event_edit($evtp)
{
$recordId = $evtp;
// Fetch entity using Doctrine
$product = EntityModel::find('Product', $recordId);
if (!$product) {
$this->page->forward(getThisGateURL());
return;
}
// Fill form using ComponentMapper
$form = $this->frtEdit->getComponent("productForm");
ComponentMapper::fillFromEntity($form, $product, $this->fieldMapping);
$this->frtEdit->addProp("formTitle", "Edit Product");
$this->frtEdit->addProp("isEditMode", true);
// Set form action dynamically
$submitUrl = getThisGateURL('save', $recordId);
$this->frtEdit->addProp("formAction", $submitUrl);
$this->setFrontFile($this->frtEdit);
}
/**
* URL: product-save.html (POST) - Save product
*/
public function page_save()
{
$form = $this->frtEdit->getComponent("productForm");
$recID = $form->getRecID();
if ($recID == "") {
// INSERT - Create new entity
$product = ComponentMapper::createFromForm($form, 'Product', $this->fieldMapping);
$product->setCreatedAt(new \DateTime());
EntityModel::save($product);
$newId = $product->getId();
echo "Product created with ID: " . $newId;
} else {
// UPDATE - Load existing and update
$product = EntityModel::find('Product', $recID);
// Update fields from form
$data = ComponentMapper::extractToArray($form, $this->fieldMapping);
foreach ($data as $field => $value) {
$setter = 'set' . ucfirst($field);
if (method_exists($product, $setter)) {
$product->$setter($value);
}
}
EntityModel::save($product);
echo "Product updated!";
}
// Redirect back to list
$this->page->forward(getThisGateURL());
}
/**
* URL: product-delete-5.html - Delete product
*/
public function page_event_delete($evtp)
{
$product = EntityModel::find('Product', $evtp);
if ($product) {
EntityModel::delete($product);
echo "Product deleted!";
}
$this->page->forward(getThisGateURL());
}
/**
* Advanced: Query with filters using Doctrine
*/
public function page_event_search($evtp)
{
$keyword = $evtp;
// Use Doctrine Query Builder
$products = EntityModel::query('Product',
"SELECT p FROM Product p WHERE p.name LIKE :keyword OR p.description LIKE :keyword"
);
$html = "";
foreach ($products as $product) {
$html .= "<div class='product'>";
$html .= "<h3>{$product->getName()}</h3>";
$html .= "<p>{$product->getDescription()}</p>";
$html .= "</div>";
}
$this->frtList->getComponent("divProducts")->setInnerHTML($html);
$this->setFrontFile($this->frtList);
}
}
```
### **Step 4: FrontFile with Dynamic URLs**
```html
<!-- product_list.front -->
<div class="container">
<h1>Product Catalog</h1>
<!-- Dynamic create URL -->
<a href="##{$createUrl}#" class="btn btn-primary">Add New Product</a>
<!-- Product list -->
<div id="divProducts" runat="server"></div>
</div>
```
```html
<!-- product_edit.front -->
<div class="container">
<h1>##{$formTitle}#</h1>
<!-- Dynamic form action -->
<form id="productForm" runat="server" action="##{$formAction}#">
<input id="txtId" runat="server" type="hidden" dfield="id" />
<label>Name:</label>
<input id="txtName" runat="server" type="text" dfield="name"
fui-setRequired="" fui-setMinLen="3" />
<label>Price:</label>
<input id="txtPrice" runat="server" type="number" dfield="price" step="0.01" />
<label>Category:</label>
<select id="sltCategory" runat="server" dfield="category">
<option value="">Select Category</option>
<option value="Electronics">Electronics</option>
<option value="Clothing">Clothing</option>
<option value="Books">Books</option>
</select>
<label>Description:</label>
<textarea id="txaDescription" runat="server" dfield="description"></textarea>
<button type="submit">Save</button>
</form>
</div>
```
---
## **12.6 Comparison: Page Methods vs Third-Party Models**
| Feature | Page Helper Methods | Third-Party (Doctrine) |
|---------|---------------------|------------------------|
| **Setup** | No extra setup | Requires Doctrine installation |
| **Code complexity** | Low | Medium |
| **Database support** | Single (configured in comp.php) | Multiple databases |
| **Relationships** | Manual joins | Built-in entity relationships |
| **Migrations** | Manual SQL | Doctrine migrations |
| **Performance** | Good for simple apps | Optimized for complex queries |
| **Learning curve** | Low | Higher |
---
## **12.7 Chapter Summary**
1. **Page helper methods** use `getAllChildren()` to iterate form components and bind to database
2. **Component flags** - `dfield`, `dtable`, `blnDontFill` control database binding behavior
3. **Third-party integration** requires creating wrapper classes and mappers
4. **Dynamic URLs** - Always use `getThisGateURL()` instead of hardcoded paths
5. **Entity mapping** - Map Doctrine entities to components using explicit mapping or auto-detection
---
## **12.8 What's Next?**
Now you have a complete understanding of SartajPHP's database integration options. You can:
- Use built-in Page methods for simple CRUD operations
- Integrate Doctrine/ELOQUENT for complex applications
- Build custom wrappers for other ORMs
Continue to build your real-world applications with these tools!
---
*End of Book 2*