Fix bin/publish: use correct .env path for rspade_system Fix bin/publish script: prevent grep exit code 1 from terminating script 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
263 lines
8.4 KiB
Plaintext
Executable File
263 lines
8.4 KiB
Plaintext
Executable File
RSpade Models (Rsx_Model_Abstract)
|
|
|
|
OVERVIEW
|
|
|
|
All RSX models must extend Rsx_Model_Abstract, which provides:
|
|
- Enum system with magic properties and methods
|
|
- Mass assignment prevention (explicit field assignment only)
|
|
- Eager loading prevention (explicit queries only)
|
|
- Ajax ORM fetch() system
|
|
- Automatic boolean casting for TINYINT(1) columns
|
|
- Relationship management via #[Relationship] attribute
|
|
|
|
ENUM SYSTEM
|
|
|
|
Define enums using public static $enums property:
|
|
|
|
public static $enums = [
|
|
'status_id' => [
|
|
1 => ['constant' => 'STATUS_ACTIVE', 'label' => 'Active', 'order' => 1],
|
|
2 => ['constant' => 'STATUS_INACTIVE', 'label' => 'Inactive', 'order' => 2]
|
|
],
|
|
'role_id' => [
|
|
1 => ['constant' => 'ROLE_ADMIN', 'label' => 'Administrator'],
|
|
2 => ['constant' => 'ROLE_USER', 'label' => 'User']
|
|
]
|
|
];
|
|
|
|
Magic Properties (Instance):
|
|
- $model->status_id_label - Get label for current enum value
|
|
- $model->status_id_constant - Get constant name for current value
|
|
- $model->status_id_order - Get sort order for current value
|
|
- $model->status_id_enum_val - Get all properties for current value
|
|
|
|
Magic Methods (Static):
|
|
- Model::status_id_enum() - Get all enum definitions (sorted by 'order')
|
|
- Model::status_id_enum_select() - Get key/label pairs for dropdowns
|
|
- Model::status_id_enum_ids() - Get all possible enum values
|
|
|
|
Enum properties in $enums can include any custom keys (label, order, constant,
|
|
color, icon, etc.). All properties are exported to JavaScript via toArray().
|
|
|
|
Optional 'selectable' property (default true) controls dropdown visibility:
|
|
2 => ['constant' => 'STATUS_ARCHIVED', 'label' => 'Archived', 'selectable' => false]
|
|
|
|
MASS ASSIGNMENT PREVENTION
|
|
|
|
Mass assignment is explicitly prohibited. Use explicit field assignment:
|
|
|
|
// CORRECT
|
|
$model = new User_Model();
|
|
$model->name = $request->input('name');
|
|
$model->email = $request->input('email');
|
|
$model->save();
|
|
|
|
// WRONG - throws MassAssignmentException
|
|
$model = User_Model::create(['name' => 'John', 'email' => 'john@example.com']);
|
|
$model->fill(['name' => 'John']);
|
|
$model->update(['name' => 'John']);
|
|
|
|
Blocked methods: fill(), forceFill(), create(), firstOrCreate(), firstOrNew(),
|
|
updateOrCreate(), update()
|
|
|
|
EAGER LOADING PREVENTION
|
|
|
|
All forms of eager loading throw exceptions. Use explicit queries:
|
|
|
|
// WRONG - throws exception
|
|
$users = User_Model::with('posts')->get();
|
|
$user->load('posts');
|
|
|
|
// CORRECT - explicit queries
|
|
$users = User_Model::all();
|
|
foreach ($users as $user) {
|
|
$posts = Post_Model::where('user_id', $user->id)->get();
|
|
}
|
|
|
|
Rationale: Premature optimization is the root of all evil. When you have
|
|
thousands of concurrent users in production and N+1 queries become a real
|
|
bottleneck, you'll be motivated enough to remove these restrictions yourself.
|
|
|
|
AJAX ORM (Model Fetch System)
|
|
|
|
Models can opt-in to client-side fetching by implementing fetch() with
|
|
#[Ajax_Endpoint_Model_Fetch] attribute:
|
|
|
|
#[Ajax_Endpoint_Model_Fetch]
|
|
public static function fetch($id)
|
|
{
|
|
// Check authorization
|
|
if (!RsxAuth::check()) {
|
|
return false;
|
|
}
|
|
|
|
// Fetch single record
|
|
$model = static::find($id);
|
|
return $model ?: false;
|
|
}
|
|
|
|
JavaScript usage:
|
|
const product = await Product_Model.fetch(1);
|
|
const products = await Product_Model.fetch([1, 2, 3]);
|
|
|
|
Framework automatically splits array IDs into individual fetch() calls for
|
|
security (no mass fetching).
|
|
|
|
See: php artisan rsx:man model_fetch
|
|
|
|
RELATIONSHIPS
|
|
|
|
Define relationships using #[Relationship] attribute to prevent Laravel from
|
|
auto-detecting non-relationship methods:
|
|
|
|
#[Relationship]
|
|
public function posts()
|
|
{
|
|
return $this->hasMany(Post_Model::class);
|
|
}
|
|
|
|
Only methods with #[Relationship] are considered relationships. This prevents
|
|
methods like is_active() from being mistaken for relationships.
|
|
|
|
Note: Eager loading is still blocked. Relationships are supported but must be
|
|
loaded with explicit queries.
|
|
|
|
AUTOMATIC BOOLEAN CASTING
|
|
|
|
TINYINT(1) columns are automatically cast to boolean without manual $casts
|
|
definition. Framework consults manifest database metadata to detect boolean
|
|
columns at runtime.
|
|
|
|
Manual $casts entries take precedence over automatic detection.
|
|
|
|
NEVER EXPORT FIELDS
|
|
|
|
Exclude sensitive fields from toArray() and Ajax responses:
|
|
|
|
protected $neverExport = ['password', 'api_token', 'remember_token'];
|
|
|
|
Fields in $neverExport are automatically removed before JavaScript export.
|
|
|
|
UTILITY METHODS
|
|
|
|
Static Methods:
|
|
- get_table_static() - Get table name without instantiating model
|
|
- getColumns() - Get array of column names for model's table
|
|
- hasColumn($column) - Check if column exists
|
|
- get_relationships() - Get array of relationship method names
|
|
- clear_table_cache() - Clear query cache for table
|
|
- clear_all_caches() - Clear all query caches
|
|
|
|
Instance Methods:
|
|
- make_new() - Create new model instance (recommended over new Model())
|
|
- get_mass_assignment_example() - Get helpful example code for this model
|
|
|
|
MODEL ORGANIZATION WITH TRAITS
|
|
|
|
Large models can be split into domain-specific trait files. This organization
|
|
strategy is primarily designed for models, though traits can be used elsewhere
|
|
in RSX applications.
|
|
|
|
Naming Convention:
|
|
|
|
Trait files should be prefixed with the model name they elaborate on:
|
|
Users_Model -> Users_Model_Authentication (trait for auth methods)
|
|
Users_Model -> Users_Model_Relationships (trait for relationship methods)
|
|
Users_Model -> Users_Model_Permissions (trait for permission logic)
|
|
|
|
This makes it immediately clear which model a trait belongs to.
|
|
|
|
Organization Strategy:
|
|
|
|
Group methods by problem domain within separate trait files:
|
|
- Authentication/authorization logic
|
|
- Query methods for specific use cases
|
|
- Relationship definitions
|
|
- Business logic for specific features
|
|
- Computed properties and accessors
|
|
|
|
Example Implementation:
|
|
|
|
// rsx/models/traits/users_model_authentication.php
|
|
trait Users_Model_Authentication
|
|
{
|
|
public static function find_by_email(string $email)
|
|
{
|
|
return static::where('email', $email)->first();
|
|
}
|
|
|
|
public static function find_by_api_token(string $token)
|
|
{
|
|
return static::where('api_token', $token)->first();
|
|
}
|
|
|
|
public function verify_password(string $password): bool
|
|
{
|
|
return password_verify($password, $this->password);
|
|
}
|
|
}
|
|
|
|
// rsx/models/traits/users_model_relationships.php
|
|
trait Users_Model_Relationships
|
|
{
|
|
#[Relationship]
|
|
public function posts()
|
|
{
|
|
return $this->hasMany(Posts_Model::class);
|
|
}
|
|
|
|
#[Relationship]
|
|
public function profile()
|
|
{
|
|
return $this->hasOne(User_Profiles_Model::class);
|
|
}
|
|
}
|
|
|
|
// rsx/models/users_model.php
|
|
class Users_Model extends Rsx_Model_Abstract
|
|
{
|
|
use Users_Model_Authentication;
|
|
use Users_Model_Relationships;
|
|
|
|
protected $table = 'users';
|
|
public static $enums;
|
|
protected $neverExport = ['password', 'api_token'];
|
|
}
|
|
|
|
Manifest Integration:
|
|
|
|
The manifest system automatically discovers and indexes methods from traits:
|
|
- Trait files are loaded before classes during manifest build
|
|
- Methods from traits appear in the model's metadata
|
|
- IDE helpers correctly locate trait method definitions
|
|
- Code quality rules validate trait methods same as class methods
|
|
|
|
Benefits:
|
|
|
|
- Keep models focused and maintainable
|
|
- Group related functionality by problem domain
|
|
- Easier to navigate large models with hundreds of methods
|
|
- Clear separation between different aspects of model behavior
|
|
- Traits are discovered and validated automatically by manifest
|
|
|
|
NAMING CONVENTIONS
|
|
|
|
Tables:
|
|
- Plural snake_case (users, user_logins, product_categories)
|
|
- Always include 'id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY'
|
|
|
|
Columns:
|
|
- snake_case for all column names
|
|
- Foreign keys must suffix with '_id' (user_id, site_id)
|
|
- Booleans must prefix with 'is_' (is_active, is_published)
|
|
- Timestamps must suffix with '_at' (created_at, updated_at, deleted_at)
|
|
- Use BIGINT for all integers, TINYINT(1) for booleans only
|
|
- All text columns use UTF-8 (utf8mb4_unicode_ci collation)
|
|
|
|
SEE ALSO
|
|
|
|
php artisan rsx:man model_fetch - Ajax ORM fetch system
|
|
php artisan rsx:man model_normalization - Database normalization process
|
|
php artisan rsx:man enums - Complete enum system documentation
|
|
php artisan rsx:man migrations - Migration system and safety
|