What's New in DataMapper 2.0
DataMapper 2.0 brings modern PHP patterns and powerful new features while maintaining 100% backward compatibility with version 1.x.
Overview
Fully Backward Compatible
All your existing DataMapper 1.x code continues to work without any changes!
DataMapper 2.0 focuses on three key areas:
- Developer Experience - Modern, chainable syntax
- Performance - Eager loading and caching
- Productivity - Traits and collection methods
Major Features
Eager Loading with Constraints
Eliminate N+1 queries and optimize relationship loading:
// Load users with their published posts
$users = (new User())
->with([
'post' => function($q) {
$q->where('published', 1)
->order_by('views', 'DESC')
->limit(5);
}
])
->get();
// Posts are already loaded - no extra queries!
foreach ($users as $user) {
foreach ($user->post as $post) {
echo $post->title;
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Performance Impact:
- Before: 101 queries (1 + 100 N+1)
- After: 2 queries
- Improvement: 98% reduction!
Collections
Work with query results using powerful collection methods:
$users = (new User())
->where('active', 1)
->collect();
// Filter
$adults = $users->filter(fn($u) => $u->age >= 18);
// Map
$names = $users->map(fn($u) => $u->first_name . ' ' . $u->last_name);
// Pluck
$ids = $users->pluck('id');
$emails = $users->pluck('email');
// Aggregate
$totalCredits = $users->sum('credits');
$avgAge = $users->avg('age');
// First/Last
$first = $users->first();
$last = $users->last();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Migrating gradually
get() still returns the model instance (with $this->all populated) so legacy controllers keep working. When you're ready for the query builder API, swap get() for collect() or the other result helpers (pluck(), value(), first()) on a per-call basis.
Query Caching
Cache expensive queries automatically:
// Cache for 1 hour
$users = (new User())
->where('active', 1)
->cache(3600)
->get();
// Cache with custom key
$users = (new User())
->where('status', 'premium')
->cache(3600, 'premium_users')
->get();
// Clear cache
(new User())->clearCache('premium_users');2
3
4
5
6
7
8
9
10
11
12
13
14
Soft Deletes
Never lose data with soft delete support:
use SoftDeletes;
class User extends DataMapper {
use SoftDeletes;
}
// Soft delete (sets deleted_at timestamp)
$user = (new User())->find(1);
$user->delete();
// Query without deleted records (automatic)
$users = (new User())->get();
// Include deleted records
$allUsers = (new User())->with_softdeleted()->get();
// Only deleted records
$deleted = (new User())->only_softdeleted()->get();
// Restore soft-deleted record
$user->restore();
// Permanently delete
$user->force_delete();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Automatic Timestamps
Never manually manage created_at and updated_at again:
use HasTimestamps;
class User extends DataMapper {
use HasTimestamps;
}
// Automatically sets created_at
$user = new User();
$user->username = 'john';
$user->save(); // created_at = now()
// Automatically updates updated_at
$user->email = 'john@example.com';
$user->save(); // updated_at = now()2
3
4
5
6
7
8
9
10
11
12
13
14
Attribute Casting
Automatically cast database values to proper PHP types:
use AttributeCasting;
class User extends DataMapper {
use AttributeCasting;
protected $casts = [
'id' => 'int',
'active' => 'bool',
'credits' => 'float',
'settings' => 'json',
'last_login' => 'datetime'
];
}
$user = (new User())->find(1);
// Automatic type casting
var_dump($user->active); // bool(true) not string "1"
var_dump($user->credits); // float(99.99) not string "99.99"
var_dump($user->settings); // array(...) not string "{...}"
var_dump($user->last_login); // DateTime object2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Streaming Results
Process massive datasets efficiently with generators:
// Stream millions of records with minimal memory
(new User())->stream(function($user) {
// Process each user
echo $user->username . "\n";
// Update user
$user->last_processed = date('Y-m-d H:i:s');
$user->save();
});
// Chunk processing
(new User())->chunk(1000, function($users) {
foreach ($users as $user) {
// Process batch of 1000 users
}
});2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Advanced Query Building
Build complex queries with ease:
// Subqueries
$users = (new User())
->whereIn('id', function($subquery) {
$subquery->select('user_id')
->from('orders')
->where('total >', 1000);
})
->get();
// Complex joins
$users = (new User())
->select('users.*, COUNT(posts.id) as post_count')
->join('posts', 'posts.user_id = users.id', 'left')
->groupBy('users.id')
->having('post_count >', 10)
->get();
// Conditional queries
$query = (new User())->where('active', 1);
if ($searchTerm) {
$query->where('username LIKE', "%{$searchTerm}%");
}
if ($minAge) {
$query->where('age >=', $minAge);
}
$users = $query->get();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Comparison Table
| Feature | DataMapper 1.x | DataMapper 2.0 |
|---|---|---|
| Syntax | Traditional | Modern query builder + Traditional |
| Eager Loading | Basic | With constraints |
| Related Columns | include_related() flattening | with() + accessors/attributes |
| Collections | No | Yes |
| Query Caching | No | Built-in |
| Soft Deletes | Manual | Trait |
| Timestamps | Manual | Trait |
| Type Casting | Manual | Automatic |
| Streaming | No | Yes |
| PHP Version | 5.6 - 7.4 | 7.4 - 8.3+ |
| Performance | Good | Excellent |
Legacy API Quick Reference
| If you used this in 1.x… | Use this in 2.0 | Why it’s better |
|---|---|---|
$user->include_related('company') | (new User())->with('company') | Loads full related objects, supports constraints, fewer queries |
$user->include_related('company', 'name') | Access via accessor/attribute on eager-loaded relation ($user->company->name) | Keeps data normalized, no column collisions |
$config['auto_populate_has_one'] = TRUE | Keep auto-populate disabled and call with() only when needed | Prevents hidden N+1 queries, reduces memory usage |
Manual JSON decoding (json_decode($user->settings)) | AttributeCasting trait with $casts = ['settings' => 'json'] | Automatic hydration + serialization |
Manual timestamp fields ($user->created_at = date(...)) | HasTimestamps trait | Ensures consistent timestamps |
Custom logger wrappers (DMZ_Logger::debug) | dmz_log_message('debug', ...) (delegates to CI log_message) | Single logging pipeline, respects CI thresholds |
These replacements are additive—you can adopt them gradually while legacy code continues to run.
Migration Path
You can adopt 2.0 features gradually:
Phase 1: Drop-in Replacement
// Just replace library files
// Everything works as before
$user = new User();
$user->get();2
3
4
Phase 2: Add Traits
use HasTimestamps, SoftDeletes;
class User extends DataMapper {
use HasTimestamps, SoftDeletes;
}2
3
4
5
Phase 3: Modern Query Builder Syntax
// Start using the chainable query builder
$users = (new User())->where('active', 1)->get();2
Phase 4: Eager Loading
// Optimize with eager loading
$users = (new User())->with('post')->get();2
Real-World Impact
Before DataMapper 2.0
// E-commerce: Get customers with recent orders
$customers = new Customer();
$customers->where('status', 'premium');
$customers->order_by('total_spent', 'DESC');
$customers->limit(50);
$customers->get();
// N+1 problem!
foreach ($customers as $customer) {
$customer->order->where('created_at >', date('Y-m-d', strtotime('-30 days')));
$customer->order->get(); // +1 query per customer!
foreach ($customer->order as $order) {
echo $order->total;
}
}
// Total: 51 queries (1 + 50)2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
After DataMapper 2.0
// Same functionality, 96% fewer queries!
$customers = (new Customer())
->with([
'order' => function($q) {
$q->where('created_at >', date('Y-m-d', strtotime('-30 days')))
->order_by('created_at', 'DESC');
}
])
->where('status', 'premium')
->order_by('total_spent', 'DESC')
->limit(50)
->cache(1800) // Cache for 30 minutes
->get();
foreach ($customers as $customer) {
foreach ($customer->order as $order) {
echo $order->total;
}
}
// Total: 2 queries!2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Getting Started
Ready to upgrade? Follow our guide:
- Requirements - Check compatibility
- Upgrading - Step-by-step upgrade guide
- Query Builder - Learn modern syntax
Feature Deep Dives
Start Small
You don't need to adopt everything at once! Start with the modern query builder, then gradually add eager loading and other features as needed.
Questions?
Check our FAQ or GitHub Discussions