Product controller:

```
class ProductController extends BaseController
{
	public function actionIndex(): string
	{
		$searchModel = new ProductSearch();
		$dataProvider = $searchModel->search($this->request->queryParams);
		$dataProvider->pagination = ['pageSize' => 10];
				
		return $this->render('index', [
			'searchModel' => $searchModel,
			'dataProvider' => $dataProvider,
		]);
	}
	
	public function actionView(int $id): string
	{
		$model = $this->findModel($id);
		
		return $this->render('view', [
			'model' => $model,
			'formModal' => $formModal,
			'formCartAdd' => $formCartAdd,
		]);
	}
	
	public function actionCreate(): Response|string
	{
		$model = new Product();
		if (!$model)
		{
			throw new YiiWebNotFoundHttpException('Product not found');
		}
		
		$request = \Yii::$app->request;
		
		\Yii::error('POST data (before load): ' . json_encode($request->post()), 'debug');
		
		if (($request->isPost) && ($model->load($request->post())))
		{
			\Yii::error('Model after load: ' . json_encode($model->attributes), 'debug');
			
			if ($model->validate() && $model->save())
			{
				\Yii::error('Saved product ID: ' . $model->id . ', purchase_options: ' . $model->purchase_options, 'debug');
			
				$model->refresh();
				\Yii::error('Saved purchase_options: ' . $model->purchase_options, 'debug');
			
				return $this->redirect(['view', 'id' => $model->id]);
			}
		}
		else
		{
			// Pre-populate default data
			$model->loadDefaultValues();
		}
		
		return $this->render('create', [
			'model' => $model,
		]);
	}
	
	public function actionUpdate(int $id): Response|string
	{
		$model = $this->findModel($id);
		$request = \Yii::$app->request;
		
		\Yii::error('POST data (before load): ' . json_encode($request->post()), 'debug');
		
		if (($request->isPost) && ($model->load($request->post())))
		{
			\Yii::error('Model after load: ' . json_encode($model->attributes), 'debug');
			
			if ($model->validate() && $model->save())
			{
				\Yii::error('Saved product ID: ' . $model->id . ', purchase_options: ' . $model->purchase_options, 'debug');
				
				$model->refresh();
				\Yii::error('Saved purchase_options: ' . $model->purchase_options, 'debug');
				
				return $this->redirect(['view', 'id' => $model->id]);
			}
		}
		
		return $this->render('update', [
			'model' => $model,
		]);
	}
	
	protected function findModel(int $id): Product
	{
		if ($this->_isGuest)
		{
			return $this->goHome();
		}
		
		$model = Product::findOne(['id' => $id]);
		if ($model !== null)
		{
			\Yii::error('DetailView purchase_options: ' . var_export($model->purchase_options, true), 'debug');
		
			return $model;
		}
		
		//throw new YiiWebNotFoundHttpException(ErrorMessageHelper::badRequest());
		$msg = \Yii::t('app', 'No Product found with `{value}`', ['value' => $id]);
		throw new YiiWebNotFoundHttpException($msg);
	}
}
```

Product model
```
namespace app\models;

/**
* This is the model class for table "{{%product}}".
*
* @property int $id
* @property int|null $category_id
* @property string $category_title
* @property int $status
* @property string $title
* @property string $artist
* @property string|null $dimensions
* @property string|null $medium
* @property int|null $year
* @property string|null $slug
* @property string $body
* @property int $quantity
* @property string|null $image_header
* @property string $product_image_one
* @property string|null $product_image_two
* @property string|null $product_image_three
* @property int $purchase_options
* @property int $price
* @property tinyInt $hide_price
* @property string $meta_description
* @property string|null $meta_keywords
* @property int $created_by
* @property int|null $updated_by
* @property int $created_at
* @property int|null $updated_at
*
* @property Cart[] $carts
* @property ProductCategory $category
* @property User $createdBy
* @property OrderItem[] $orderItems
* @property User $updatedBy
*/
class Product extends ActiveRecord
{
	use BitmaskTrait;
	
	public const PURCHASE_OPTIONS_CART = 1;// 2^0
	public const PURCHASE_OPTIONS_INQUIRE = 2;// 2^1
	public const PURCHASE_OPTIONS_BUYNOW = 4;// 2^2
	
	/**
	* Purchase options bitmask stored in DB, nullable to avoid uninitialized error.
	* @var int|null
	*/
	public ?int $purchase_options = null;
	
	/**
	* Virtual attribute to hold array for form checkbox binding.
	* @var int[]|null
	*/
	private ?array $_purchaseOptionsArray = null;
	
	public function init()
	{
		/**
		* Always call the parent init method first to ensure any
		* initialization logic defined in the base class is executed first.
		*/
		parent::init();
		
		// ... initialization after configuration is applied...
		
		$this->_productSummaryLength = ParamHelper::get('product.summaryLength');
		
		/**
		* Turn off default for purchase_options in init() (optional)
		*
		* If you're unsure why your model isNewRecord acts weirdly, remove the default assignment 
		* from init() and rely 100% on validation default and DB default value instead.
		*
		\Yii::info(
			'Init called. isNewRecord=' . ($this->isNewRecord ? 'true' : 'false') 
				. ', purchase_options=' . var_export($this->purchase_options, true), 'debug'
		);
		
		if ($this->isNewRecord && $this->purchase_options === null)
		{
			$this->purchase_options = self::PURCHASE_OPTIONS_CART | self::PURCHASE_OPTIONS_INQUIRE;
		}//*/
	}
	
	public static function tableName(): string
	{
		return '{{%product}}';
	}
	
	public function rules(): array
	{
		return [			
			/**
			* Validation and Rules for purchase_options
			*/
			['purchase_options', 'default', 'value' => self::PURCHASE_OPTIONS_CART | self::PURCHASE_OPTIONS_INQUIRE],
			['purchase_options', 'safe'],// Safe so mass assignment is allowed
			['purchase_options', 'validatePurchaseOptions'],
			['purchaseOptionsArray', 'safe'],// Also allow purchaseOptionsArray for form binding as safe
			
			// Ensures `category_id` in this model refers to an existing `id` in the ProductCategory model.
			['category_id', 'exist', 'skipOnEmpty' => false, 'skipOnError' => true, 'targetClass' => ProductCategory::class, 'targetAttribute' => ['category_id' => 'id']],
		];
	}
	
	/**
	* Validates purchase_options bitmask correctness and business rules.
	* @param string $attribute
	* @param array $params
	*
	* Important: No type hints on params for Yii compatibility.
	*
	* Note: Do NOT add parameter(s) type hints. This method overrides an interface 
	* without type hints on parameters, adding types will cause PHP compatibility errors. 
	* Only docblock types to indicate expected input types.
	*/
	public function validatePurchaseOptions($attribute, $params = []): void
	{
		$validFlags = array_sum(self::getPurchaseOptionFlags());
		
		if (($this->$attribute & ~$validFlags) !== 0)
		{
			$this->addError($attribute, 'Product options contain invalid flags.');
			
			return;
		}
		
		if ($this->hasPurchaseOptions(self::PURCHASE_OPTIONS_CART) &&
			$this->hasPurchaseOptions(self::PURCHASE_OPTIONS_BUYNOW)
		)
		{
			$this->addError($attribute, 'Product purchase options cannot include both Cart and Buy Now.');
		}
	}
	
	public function attributeLabels()
	{
		return [
			'purchase_options' => \Yii::t('app', 'Purchase Options'),
		];
	}
	
	/**
	* Returns the map of purchase option keys to labels for UI.
	* @return array
	*/
	public function getPurchaseOptionsLabels(): array
	{
		return [
			self::PURCHASE_OPTIONS_CART => \Yii::t('app', 'Cart'),
			self::PURCHASE_OPTIONS_INQUIRE => \Yii::t('app', 'Inquire'),
			self::PURCHASE_OPTIONS_BUYNOW => \Yii::t('app', 'Buy Now'),
		];
	}
	
	public function beforeValidate(): bool
	{
		if (!parent::beforeValidate())
		{
			return false;
		}
		
		/**
		* Purchase Options Array to Bitmask
		* Convert purchaseOptionsArray from form into bitmask int before validation.
		*/
		if ($this->_purchaseOptionsArray !== null)
		{
			$this->purchase_options = $this->arrayToBitmask($this->_purchaseOptionsArray);
		}
		elseif (is_array($this->purchase_options))
		{
			// Defensive: convert in case purchase_options is array (usually from form POST)
			$this->purchase_options = $this->arrayToBitmask($this->purchase_options);
		}
		else
		{
			// Ensure integer type
			$this->purchase_options = (int) $this->purchase_options;
		}
		
		return true;
	}
	
	/**
	* Defensive getter to return purchase_options with default if null.
	* @return int
	*/
	public function getPurchaseOptions(): int
	{
		return $this->purchase_options ?? 0;
	
		/*if ($this->purchase_options !== null)
		{
			return $this->purchase_options;
		}
		
		if ($this->isNewRecord)
		{
			return self::PURCHASE_OPTIONS_CART | self::PURCHASE_OPTIONS_INQUIRE;
		}
		
		// Safeguard: if DB returned null, forced to zero to avoid fallback guess.
		return 0;//*/
	
		//return $this->purchase_options ?? (self::PURCHASE_OPTIONS_CART | self::PURCHASE_OPTIONS_INQUIRE);
	}
	
	/**
	* Returns all possible purchase option flags.
	* Useful in forms and validation.
	*/
	public static function getPurchaseOptionFlags(): array
	{
		return [
			self::PURCHASE_OPTIONS_CART,
			self::PURCHASE_OPTIONS_INQUIRE,
			self::PURCHASE_OPTIONS_BUYNOW,
		];
	}
	
	/**
	* Get Purchase Options for Checkboxes
	* @return array
	*
	* Helper to get purchase options as array for checkbox input in forms.
	*/
	public function getPurchaseOptionsForCheckboxes(): array
	{
		return $this->bitmaskToArray(
			$this->getPurchaseOptions(),
			self::getPurchaseOptionFlags()
		);
	}
	
	/**
	* Returns the purchase options as an array of flags.
	* If the internal cache is null, it converts current bitmask.
	* Useful for checkboxes.
	*/
	public function getPurchaseOptionsArray(): array
	{
		if ($this->_purchaseOptionsArray === null)
		{
			$this->_purchaseOptionsArray = $this->getPurchaseOptionsForCheckboxes();
		}
		
		return $this->_purchaseOptionsArray;
	}
	
	/**
	* Accepts an array of flags and sets both virtual array and bitmask.
	*/
	public function setPurchaseOptionsArray(array $values): void
	{
		\Yii::error('setPurchaseOptionsArray called: ' . json_encode($values));
	
		$this->_purchaseOptionsArray = $values;
		$this->purchase_options = $this->arrayToBitmask($values);
	}
	
	/**
	* Has Purchase Options
	* @param int $flag
	* @return bool
	*
	* Instead of calling hasStatus() twice in the if condition, you can directly 
	* check using a bitwise operation:
	*
	*	// Fetch the message with a specific ID.
	* 	$product = Product::findOne($id);
	*
	* 	if (($product->purchase_options & (Product::PURCHASE_OPTIONS_CART | Product::PURCHASE_OPTIONS_INQUIRE)) === 
	*		(Product::PURCHASE_OPTIONS_CART| Product::PURCHASE_OPTIONS_INQUIRE)
	*	)
	*	{
	*		echo "This product is inquire only.";
	*	}
	*
	* In this alternative approach:
	*
	* 	The expression Product::PURCHASE_OPTIONS_CART | Product::PURCHASE_OPTIONS_INQUIRE
	*	combines both statuses (using the bitwise OR).
	*
	* 	The condition checks if both statuses are set within the status field of 
	*	the message.
	*
	* 	if ($model->hasPurchaseOptions(Product::PURCHASE_OPTIONS_INQUIRE))
	*	{
	*		// do something
	*	}
	*/
	public function hasPurchaseOptions(int $flag): bool
	{
		return ($this->getPurchaseOptions() & $flag) === $flag;
	}
	
	/**
	* Add Purchase Options
	* @param int $flag
	*
	* Set Statuses (make sure to save):
	*
	*	$user->addPurchaseOptions(Product::PURCHASE_OPTIONS_CART);
	*	$user->save();// Save changes to the database.
	*/
	public function addPurchaseOptions(int $flag): void
	{
		$current = $this->purchase_options ?? 0;
		$this->purchase_options = $current | $flag;
	}
	
	/**
	* Remove Purchase Options
	* @param int $flag
	*
	* For example, if the current status is 5 (00101) and we want to remove 
	* the "Unread" status (1 or 00001), the resulting status will change to
	* 4 (00100).
	*
	* Removing Purchase Options:
	*	
	*	$user->removePurchaseOptions(Product::PURCHASE_OPTIONS_CART);
	*/
	public function removePurchaseOptions(int $flag): void
	{
		$current = $this->purchase_options ?? 0;
		$this->purchase_options = $current & (~$flag);
	}
	
	/**
	* Getter User Statuses
	* How to use:
	*
	*	$user->isActive();
	*/
	public function isCart(): bool
	{
		return $this->hasPurchaseOptions(self::PURCHASE_OPTIONS_CART);
	}
	
	public function isInquire(): bool
	{
		return $this->hasPurchaseOptions(self::PURCHASE_OPTIONS_INQUIRE);
	}
	
	public function isBuynow(): bool
	{
		return $this->hasPurchaseOptions(self::PURCHASE_OPTIONS_BUYNOW);
	}
	
	/**
	* Setters User Statuses
	* How to use:
	*
	*	$user->activateAccount();
	*
	* Note: Delete status is exclusive and clears all other flags.
	*/
	public function setCart(): void
	{
		$this->addPurchaseOptions(self::PURCHASE_OPTIONS_CART);
	}
	
	public function setInquire(): void
	{
		$this->addPurchaseOptions(self::PURCHASE_OPTIONS_INQUIRE);
	}
	
	public function setBuynow(): void
	{
		$this->addPurchaseOptions(self::PURCHASE_OPTIONS_BUYNOW);
	}
}
```

Bitmask Trait
```
namespace app\components\traits;

trait BitmaskTrait
{
	/**
	* Convert integer bitmask to array of individual flags.
	* @param int $bitmask The bitmask integer.
	* @param int[] $allFlags List of all possible flags.
	* @return int[] Array of flags present in bitmask.
	*/
	public function bitmaskToArray(int $bitmask, array $allFlags): array
	{
		$flags = [];
		
		foreach ($allFlags as $flag)
		{
			if (($bitmask & $flag) === $flag)
			{
				$flags[] = $flag;
			}
		}
		
		return $flags;
	}
	
	/**
	* Convert array of flags to integer bitmask.
	* @param int[] $flags Array of flags.
	* @return int Combined bitmask integer.
	*/
	public function arrayToBitmask(array $flags): int
	{
		$bitmask = 0;
		
		foreach ($flags as $flag)
		{
			$bitmask |= (int) $flag;
		}
		
		return $bitmask;
	}
}
```

and lastly how im trying to use this code in view files:
```
echo $form->field($model, 'purchaseOptionsArray')->checkboxList([
	Product::PURCHASE_OPTIONS_CART => 'Cart',
	Product::PURCHASE_OPTIONS_INQUIRE => 'Inquire',
	Product::PURCHASE_OPTIONS_BUYNOW => 'Buy Now',
])
```

or
```
echo $form->field($model, 'purchaseOptionsArray')->checkboxList($model->getPurchaseOptionsLabels());
```

within a activeForm in the _form.php file for create.php and update.php

in view.php gridview or detailview
```
		'purchase_options',
		
		[
			'attribute' => 'purchase_options',
			'format' => 'raw',
			'value' => function ($model)
			{
				$flags = $model->getPurchaseOptionsForCheckboxes();
				
				$labels = [];
				
				foreach ($flags as $flag)
				{
					$labels[] = Html::encode($model->getPurchaseOptionsLabels()[$flag] ?? 'Unknown');
				}
				
				return implode(', ', $labels);
			},
		],
```
Why im sharing this code. It does not work and here what it does:
- Defaults are always saved, I've checked the database and purchase_options is always set to "3".
- when creating a new product I set purchase_options to "buy now". Saved. Checked the database via phpMyAdmin purchase_options is "3". Here
is the error log for all of this:

2025-07-04 15:01:13 [::1][1][-][error][debug] POST data (before load): []
controllers/ProductController.php:220

2025-07-04 15:02:12 [::1][1][-][error][debug] POST data (before load): {"_csrf":"-U0moqQVWYxl3OD3CoyoPbaYNJ4U6Sf6VIBtuEPpoh-eHXzLwlMw-BaGmb9y3sIN_8FSxmORa4o_xFnNKLrKeA==","Product":{"title":"test4","status":"0","category_id":"1","meta_description":"test4","meta_keywords":"test4","image_header":"","product_image_one":"http:\/\/localhost:8080\/uploads\/laduck_0b63464b5a0b6b95e2c796a3c8f293ef-1751591794-68672b72a1d4b.jpg","product_image_two":"","product_image_three":"","purchaseOptionsArray":["4"],"price":"5","hide_price":"0","quantity":"5","artist":"tester4","dimensions":"","medium":"","year":"","body":"<p>test4test4test4test4test4test4test4<br><\/p>"},"create-button":""}
controllers/ProductController.php:220
    
2025-07-04 15:02:12 [::1][1][-][error][application] setPurchaseOptionsArray called: ["4"]
models/Product.php:727
controllers/ProductController.php:223
    
2025-07-04 15:02:12 [::1][1][-][error][debug] Model after load: {"id":null,"category_id":"1","type":null,"category_title":null,"status":"0","title":"test4","artist":"tester4","dimensions":"","medium":"","year":"","slug":null,"body":"<p>test4test4test4test4test4test4test4<br><\/p>","quantity":"5","image_header":"","product_image_one":"http:\/\/localhost:8080\/uploads\/laduck_0b63464b5a0b6b95e2c796a3c8f293ef-1751591794-68672b72a1d4b.jpg","product_image_two":"","product_image_three":"","price":"5","meta_description":"test4","meta_keywords":"test4","created_by":null,"updated_by":null,"created_at":null,"updated_at":null,"purchase_options":4,"hide_price":"0"}
controllers/ProductController.php:225
    
2025-07-04 15:02:13 [::1][1][-][error][debug] Saved product ID: 6, purchase_options: 4
controllers/ProductController.php:230
    
2025-07-04 15:02:13 [::1][1][-][error][debug] Saved purchase_options: 4
controllers/ProductController.php:233

2025-07-04 15:02:13 [::1][1][-][error][debug] DetailView purchase_options: NULL
controllers/ProductController.php:393
controllers/ProductController.php:165
    
2025-07-04 15:02:13 [::1][1][-][error][debug] DetailView purchase_options: NULL
views/product/artwork.php:268
controllers/ProductController.php:185

- when loading the product update page, purchase_options is set from before, however none of the checkboxes are set.
- Validation does not seem to effect purchase_options and it should

I am looking for a edculdated understand of why purchase_options is stuck on default, if I need to refactor any code, I am not willing to
add more error logging since I already have enough and it does not solve the issue.

the entire purchase_options is broken...