Custom Database Tables
Creating Custom Database Tables with Packages
For custom database tables in Concrete CMS packages:
- Use the AXMLS format, available since the beginning of Concrete CMS.
- Employ the Doctrine XML format, compatible with the Doctrine Database Access Layer in Concrete.
- Utilize PHP Entity Classes and the Doctrine 2 ORM Library
Each method offers different levels of compatibility and functionality.
Legacy Method: db.xml in AXMLS Format
Developers experienced with Concrete CMS can use the AXMLS format for database tables. Create a db.xml file in the root directory with an XML description of the database tables. For details, see Creating and Working with db.xml Files.
Example
<?xml version="1.0"?>
<schema version="0.3">
<table name="TestDatabaseTable1">
<field name="cID" type="I" size="10">
<KEY/>
<DEFAULT value="0"/>
<UNSIGNED/>
</field>
<field name="testBoolean" type="I1">
<NOTNULL/>
<DEFAULT value="0"/>
</field>
</table>
<table name="TestDatabaseTable2">
<field name="id" type="C" size="255">
<KEY/>
<NOTNULL/>
</field>
<field name="testField" type="C" size="255">
<NOTNULL/>
</field>
<field name="records" type="I">
<DEFAULT value="0"/>
<UNSIGNED/>
</field>
<field name="data" type="X2">
</field>
</table>
</schema>
db.xml in Doctrine XML Format
Doctrine XML format, based on Doctrine DBAL,provides consistent types and nomenclature and replaces AXMLS codes like C
with more descriptive types like string
.
Advantages of Doctrine XML include:
- Supports foreign keys, comments, and features not as well covered in AXMLS.
- Easily applicable in other projects, installable via composer.
Doctrine XML Github Repository
Access the Doctrine XML library at https://github.com/concretecms/doctrine-xml.
Example
Example of a Users table in Doctrine XML:
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.concrete5.org/doctrine-xml/0.5"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.concrete5.org/doctrine-xml/0.5 https://concretecms.github.io/doctrine-xml/doctrine-xml-0.5.xsd">
<table name="Users">
<!-- [Fields and configurations omitted for brevity] -->
</table>
</schema>
Auto-Detection
Doctrine XML is recognized automatically when the XML schema includes xmlns="http://www.concrete5.org/doctrine-xml/0.5"
.
Converting from AXML Format
Convert from AXML to Doctrine XML at https://concretecms.github.io/doctrine-xml/.
Doctrine ORM Entities
Concrete now supports handling custom database tables using Doctrine ORM entities instead of direct database tables. Developers define these entities as PHP classes with annotations, which automatically generate database tables.
Requirements
Familiarity with Doctrine 2 and its ORM component is necessary.
Example
Creating a 'UserAgent' entity to store browser identifiers:
File: packages/statistics/src/Entity/UserAgent.php
<?php
namespace Concrete\Package\Statistics\Src\Entity;
class UserAgent {
protected $id;
protected $value;
protected $hash;
// [Methods omitted for brevity]
}
Annotate the PHP class to turn it into a Doctrine ORM entity:
<?php
namespace Concrete\Package\Statistics\Src\Entity;
/**
* @Entity
* @Table(name="StatisticsUserAgents")
*/
class UserAgent {
/**
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @Column(type="string")
*/
protected $value;
/**
* @Column(type="string")
*/
protected $hash;
// [Methods omitted for brevity]
}
Upon package installation, the table StatisticsUserAgents
is created automatically. Use Doctrine ORM for data persistence and managing schema changes.
Including Doctrine Entities in Your Package
Requirements
Developers must be well-versed in Doctrine 2 and its ORM component.
Standard Behavior
Entities in packages typically use:
- Annotation Driver for database info from PHP code annotations.
- For legacy \Src namespace enabled packages, the
packages/package_handle/src/
directory is scanned for entities. - Without legacy \Src namespace,
packages/your_package/src/Concrete/Entity
and$pkg->getPackageAutoloaderRegistries()
specified locations are scanned.
This is managed by Concrete\Core\Database\EntityManager\Provider\DefaultPackageProvider
.
Annotation Driver Functionality
For packages requiring Concrete CMS version 8 or above with annotated entity classes:
Before:
namespace Concrete\Package\Statistics\Src\Entity;
/**
* @Entity
* @Table(name="StatisticsUserAgents")
*/
class UserAgent {
// [Attributes with basic annotations]
}
After:
namespace Concrete\Package\Statistics\Src\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="StatisticsUserAgents")
*/
class UserAgent {
// [Attributes with @ORM prefixed annotations]
}
This change supports advanced features like Doctrine Extensions.
Customizable Behavior
To tailor package entity manager configuration:
- Use a custom package provider for specific entity locations.
- Integrate additional Doctrine types or extensions.
- Utilize alternate formats like XML or YAML for entity definitions.
Implement one of these in your package's controller:
Concrete\Core\Database\EntityManager\Provider\ProviderAggregateInterface
withgetEntityManagerProvider()
returningProviderInterface
.- Directly implement
ProviderInterface
, returningDriverInterface
objects.
Example
For a package with entities in Concrete\Package\YourPackage\Entity
and PortlandLabs\Testing\Entity
:
- Implement
ProviderAggregateInterface
in the package controller. Use
StandardPackageProvider
for the desired locations:public function getEntityManagerProvider() { return new StandardPackageProvider($this->app, $this, [ 'src/Concrete/Entity' => 'Concrete\Package\YourPackage\Entity', 'src/Testing/Entity' => 'PortlandLabs\Testing\Entity' ]); }
Other File Formats
For YAML or XML formats, explore Concrete\Core\Database\EntityManager\Provider\YamlProvider
and XmlProvider
.
Doctrine Entities in a Custom Database
Exclude Entities from Concrete's Management
In your package controller.php
, after the namespace declaration, implement ProviderInterface
to prevent Concrete from managing your entities:
use Concrete\Core\Database\EntityManager\Provider\ProviderInterface;
class Controller extends Package implements ProviderInterface
public function getDrivers() {
return [];
}
For entities managed by Concrete, separate them in a different directory, like src/Entities/Standard
, and modify getDrivers
accordingly.
Establish a Separate Database Connection
Set up a connection to your entity database. Connection setup guide. Assume the connection is named my-connection
.
Implement a Custom Entity Manager
Create your own entity manager in your package's src
directory:
<?php
namespace MyNamespace;
use Concrete\Core\Database\DatabaseStructureManager;
use Doctrine\ORM\EntityManager;
class MyEntityManager extends EntityManager {
public function refreshProxyClasses() {
$manager = new DatabaseStructureManager($this);
$manager->clearCacheAndProxies();
}
}
Initialize the Custom Entity Manager
In your package controller.php
, add:
private function registerEntityManager() {
$this->app->singleton(\MyNamespace\MyEntityManager::class, function ($app) {
// Configuration for custom entity manager
});
}
public function on_start() {
$this->registerEntityManager();
}
The custom entity manager uses annotation driver and entities in src/Entity
.
Handle Proxy Class Generation
Add methods to your package controller for install and upgrade operations:
public function install() {
$this->registerEntityManager();
$this->refreshProxyClasses();
}
public function upgrade() {
$this->registerEntityManager();
}
private function registerEntityManager() {
$this->app->make(\MyNamespace\MyEntityManager::class)->refreshProxyClasses();
}
Integrate with Concrete
Add integration for listing and refreshing entities:
private function registerEntityEvents() {
$app = $this->app;
$director = $app->make(\Symfony\Component\EventDispatcher\EventDispatcher::class);
$director->addListener('on_list_package_entities', function ($event) use ($app) {
$event->addEntityManager($app->make(\MyNamespace\MyEntityManager::class));
});
$director->addListener('on_refresh_package_entities', function ($event) use ($app) {
$app->make(\MyNamespace\MyEntityManager::class)->refreshProxyClasses();
});
}