Skip to content

Database Models ​

Introduction ​

WP Social Ninja's WPFluent framework provides a beautiful, simple ActiveRecord implementation for working with the database. Each wpsr_ table has a corresponding Model used to query and persist records — an Eloquent-like API that will feel familiar if you have used Laravel.

NOTE

WP Social Ninja also exposes global helper functions (like wpsrGetReviews() and wpsrDb()) for common data access — prefer those for simple cases. The models are documented here for internal use and advanced third-party integrations. See Global Helpers.

Built-in models ​

Models live in the core plugin at app/Models/. Each extends the app Model (WPSocialReviews\App\Models\Model), which extends the framework WPSocialReviews\Framework\Database\Orm\Model and guards ['id', 'ID'] from mass assignment.

ModelTableNotes
Reviewwpsr_reviewsrating cast to int, fields cast to json; SearchableScope
ReviewCategory 4.4.0wpsr_review_categoriesid cast to int; fillable name, slug
ReviewCategoryRelationship 4.4.0wpsr_review_category_relationshipsPivot; bulk attach/detach helpers
ReviewFormwpsr_review_formsNative/Pro review forms
Cachewpsr_cachesFeed / API response cache
OptimizeImagewpsr_optimize_imagesOptimized media records
Templatewp_postsTemplate post type; SearchableScope
Widgetwp_postsChat widget post type; SearchableScope
Notificationwp_postsNotification popups; SearchableScope
Postwp_postsGeneric post access; SearchableScope

Throughout this page we use WPSocialReviews\App\Models\Review as the example.

Retrieving Models ​

Think of each model as a powerful query builder for its table. all() returns every row:

php
use WPSocialReviews\App\Models\Review;

$reviews = Review::all();

foreach ($reviews as $review) {
    echo $review->reviewer_name;
}

Adding constraints ​

Because each model serves as a query builder, add constraints and call get():

php
$reviews = Review::where('platform_name', 'google')
    ->where('review_approved', 1)
    ->orderBy('id', 'DESC')
    ->limit(10)
    ->skip(5)
    ->get();

Retrieving Single Models / Aggregates ​

php
// By primary key
$review = Review::find(1);

// First matching the constraints
$review = Review::where('platform_name', 'facebook')->first();

// Multiple by primary key
$reviews = Review::find([1, 2, 3]);

Aggregate helpers return a scalar:

php
$count = Review::where('platform_name', 'google')->count();
$max   = Review::where('platform_name', 'google')->max('rating');

Available aggregates include count, max, min, avg, and sum.

Inserting & Updating ​

Inserts ​

php
$review = Review::create([
    'platform_name'   => 'custom',
    'reviewer_name'   => 'Jane Doe',
    'reviewer_email'  => 'jane@example.com',
    'rating'          => 5,
    'reviewer_text'   => 'Fantastic!',
    'review_approved' => 1,
    'fields'          => ['source' => 'my_addon'], // encoded to JSON via the cast
]);

Updates ​

Assign attributes and save(), or update with an array:

php
$review = Review::find(1);
$review->rating = 4;
$review->save();

// or
Review::find(1)->update([
    'rating'          => 4,
    'review_approved' => 1,
]);

Accessing Attributes ​

Access columns as properties. Casts apply automatically:

php
$review = Review::find(1);

$rating = $review->rating;   // int (cast)
$fields = $review->fields;   // array (json cast)
$name   = $review->reviewer_name;

Deleting Models ​

php
// A single instance
Review::find(1)->delete();

// By query (mass delete — no model events fired)
Review::where('review_approved', 0)->delete();

Query Scopes ​

Scopes let you reuse common constraints. WP Social Ninja ships a SearchableScope trait (app/Models/Traits/SearchableScope.php) that adds a searchBy scope. A model using it declares a $searchable array of columns, then you call the scope with the small first letter:

php
// scopeSearchBy(...) → called as searchBy(...)
$reviews = Review::searchBy('great service')->get();

Internally the scope builds an OR LIKE across the model's $searchable fields — handy for list-screen search boxes.

Relationships ​

The framework ORM supports Eloquent-style relationships (belongsTo, hasMany, …). WP Social Ninja's models are largely self-contained (a single wpsr_reviews table keyed by platform_name / source_id rather than normalized joins), so cross-model relations are few — but the capability is there if you extend the models in your own code.

Review categories 4.4.0 ​

The one normalized relation in core is many-to-many, in both directions:

php
use WPSocialReviews\App\Models\Review;
use WPSocialReviews\App\Models\ReviewCategory;

$review->categories();      // belongsToMany(ReviewCategory, …, 'review_id', 'category_id')
$category->reviews();       // belongsToMany(Review, …, 'category_id', 'review_id')

Deleting a ReviewCategory detaches it from every related row — the model's boot() registers a deleting listener that calls $category->reviews()->detach(). That listener only fires on model deletes; a mass ReviewCategory::where(...)->delete() leaves pivot rows behind.

Resolving names to IDs ​

Names arrive from the admin, CSV import, and Fluent Forms submissions, so the model owns the normalization rather than each call site:

php
// Case- and whitespace-insensitive; "Support" and "support" never fragment into two rows.
$category = ReviewCategory::findOrCreateByName('Customer Support');

// A save payload's ids and new names, resolved to confirmed IDs.
$ids = ReviewCategory::resolveIds([3, 4], ['Delivery']);

// Lookups in both directions.
$ids   = ReviewCategory::idsForNames(['Delivery', 'Support']);
$names = ReviewCategory::namesForIds([3, 4]);

// Listings — with or without relationship counts.
$all       = ReviewCategory::allOrdered();            // alphabetical, names only
$withCount = ReviewCategory::allOrderedWithCounts();  // adds `reviews_count`

resolveIds() takes ids and names as two separate lists because they cannot be told apart by value — a category named 2024 looks exactly like ID 2024. Ids are confirmed against the table, so a stale one is dropped rather than linked.

Both name and slug are capped at ReviewCategory::MAX_LENGTH (191). The cap is applied in a setNameAttribute() mutator with mb_substr(), so no write path can miss it and a multi-byte character is never split in half.

findOrCreateByName() is not atomic

The lookup and the insert are separate statements, so concurrent imports can both reach the insert with the same slug. The UNIQUE index on slug rejects the loser, and the method re-reads whatever the winner created — but if you write categories yourself, expect that race and handle the duplicate-key failure.

Bulk pivot writes ​

ReviewCategoryRelationship exposes static helpers for the bulk paths, chunked at ReviewCategoryRelationship::BATCH_SIZE:

php
use WPSocialReviews\App\Models\ReviewCategoryRelationship;

ReviewCategoryRelationship::bulkAttach([12, 13], $categoryId);
ReviewCategoryRelationship::bulkDetach([12, 13], $categoryId);
ReviewCategoryRelationship::deleteForReviews([12, 13]); // all categories, e.g. on review delete

Per-review work goes through the relation instead — $review->categories()->sync($ids). The helpers above exist for the set-based operations spanning many reviews at once, which a relation would turn into one query per review. BATCH_SIZE is 500, because a "select all" makes these id lists as long as the site's review count and the statements must stay under max_allowed_packet.

bulkAttach() uses insertOrIgnore(), so the UNIQUE (review_id, category_id) key is the dedupe mechanism — re-attaching an existing link is a no-op rather than an error.

Deleting reviews does not clear pivot rows

Review deletes are query-builder mass deletes, so no model event fires and nothing else unlinks the categories. Call deleteForReviews() wherever you delete reviews. It no-ops safely when the tables do not exist yet — it is reachable from cron, which never runs the migration (that happens when the plugin's admin page renders).

The pivot has no foreign keys, so validate that $categoryId resolves to a real category before calling; see Schema.

Using the query builder directly ​

When you don't need a full model, reach for the framework query builder via wpsrDb():

php
$rows = wpsrDb()
    ->table('wpsr_reviews')
    ->where('platform_name', 'yelp')
    ->get();

See the Query Builder page for the complete method reference.

Validate before you query

Per the project conventions, validate IDs and inputs before building a query — early return on empty values and never assume an ID is present. See Coding Conventions.

WP Social Ninja Developer Documentation