# **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*