I have a JSON file as my database, I wrote this function for creating a new user row into the file:
function create(array $data)
{
$tableFilePath = __DIR__ . "/storage/jsondb/users.json";
$fileData = json_decode(file_get_contents($tableFilePath));
$fileData[] = $data;
$fileDataJson = json_encode($fileData);
file_put_contents($tableFilePath, $fileDataJson);
}
create(['id' => rand(1, 100), 'name' => 'user' . rand(1, 100)]);
The problem is that when I call this function, I expect it inserts 1 row but it inserts 2 rows into the JSON file.
UPDATE:
my JSON file before running the code is an empty array:
[]
After running the code:
[{"id":28,"name":"user-3"},{"id":68,"name":"user-78"}]
as you can see, I executed the code one time, but it inserted two records into the JSON file.
UPDATE-3
I summarized my code because I wanted others can read it easily.
now it's my whole class that I wrote.
here is my interface:
interface CrudInterface
{
public function create(array $data) : int;
public function find(int $id): object;
public function get(array $columns,array $where):array;
public function update(array $data,array $where):int;
public function delete(array $where):int;
}
here I have abstract class BaseModel that implements CrudInterface:
abstract class BaseModel implements CrudInterface
{
protected $connection;
protected $table;
protected $pageSize = 10;
protected $attributes = [];
protected $primaryKey = 'id';
protected function __construct()
{
# if mysql => set mysql connection
}
protected function getAttribute($key)
{
if(!$key || array_key_exists($key, $this->attributes)){
return null;
}
return $this->attributes[$key];
}
}
here JsonBaseModel that extends BaseModel:
class JsonBaseModel extends BaseModel
{
private $dbFolder;
private $tableFilePath;
public function __construct()
{
$this->dbFolder = BASE_PATH . 'storage/jsondb/';
$this->tableFilePath = $this->dbFolder . $this->table . '.json';
}
private function readTable()
{
$fileData = json_decode(file_get_contents($this->tableFilePath));
return $fileData;
}
private function writeTable($fileData){
$fileDataJson = json_encode($fileData);
file_put_contents($this->tableFilePath, $fileDataJson);
}
public function create(array $newData): int
{
$fileData = $this->readTable();
$fileData[] = $newData;
$this->writeTable($fileData);
return $newData[$this->primaryKey];
}
}
and here I have class User that extends JsonBaseModel:
class User extends JsonBaseModel
{
protected $table = 'users';
}
then I made an instance of User class and call it's create() function here:
$data = ['id' => rand(1, 100),'name' => "user-".rand(1, 100)];
$userModel = new User();
$userModel->create($data);
You're passing in an array with the user details, then adding it to the current contents.
If there is already a entry in your file, you'll then have 2 entries.
Make sure that users.json is empty first.
Related
Hi there I have a class : AbstractEntityType with a protected property :
abstract class AbstractEntityType extends AbstractType {
protected static $lists = null;
public function __construct($lists = array()) {
AbstractEntityType::$lists = $lists;
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setRequired(array(
'temp', 'statut'
));
}
}
Here is another class extending the previous one :
class MyType extends AbstractEntityType {
....
}
I use a factory to create MyType class:
class SimpleFormTypeFactory {
public function createType($entity_type, $entity_stub, $lists = null) {
$type = null;
switch($entity_type) {
....
case SOMENUMTYP:
$type = new MyType($lists);
break;
}
}
I tested it locally with php 5.4 and windows with no problem but on the server (linux and php 5.3) I have this error :
Error: Cannot access protected property MyType::$lists
What is going on ? a php bug ?
Thank you
The property is protected, so you can never do this:
public function __construct($lists = array()) {
AbstractEntityType::$lists = $lists;
^^^^^^^^^^^^^^^^^^^^^^^^^^ Not allowed for a protected property, regardless where you are
}
However, when you are inside your class, you can access it directly:
public function __construct($lists = array()) {
self::$lists = $lists;
}
I have a ChildClass that extends to a ParentClass. The ParentClass has a constructor that takes two arguments __construct('value2', ParentClass::COMMON). HOwever I am trying to call the inherited newSelect() method from within the child class. So far it has not been succesfull. How would I call NewSelect() from the ChildClass? Also is it possible even though the ParentClass has a constructor that takes two parameters?
Parent
class ParentClass {
const COMMON = 'common';
protected $db;
protected $common = false;
protected $quotes = array(
'Common' => array('"', '"'),
'Value2' => array('`', '`'),
'Value3' => array('`', '`'),
'Value4' => array('`', '`'),
);
protected $quote_name_prefix;
protected $quote_name_suffix;
public function __construct($db, $common = null) {
$this->db = ucfirst(strtolower($db));
$this->common = ($common === self::COMMON);
$this->quote_name_prefix = $this->quotes[$this->db][0];
$this->quote_name_suffix = $this->quotes[$this->db][1];
}
public function newSelect() {
return $this->newInstance('Select');
}
protected function newInstance($query) {
//Some more code, not relevant to example
}
}
Child
class ChildClass extends ParentClass {
public function __construct() {
}
// Call the inherited method
private $_select = Parent::newSelect();
// Do something with it
}
// you can't do this
private $_select = Parent::newSelect();
// try this
private $_select;
public function construct($value, $common)
{
Parent::__construct($value, $common);
$this->_select = $this->newSelect();
}
// and
$obj = new ChildClass('Value2', ParentClass::COMMON); // ParentClass::COMMON - not sure why you would do this
$select = $obj->newSelect(); // this is being called in constructor, why call it again
and to be honest, I don't even know what you're trying to do but everything about it looks wrong too!
I wrote a class for module creating. That run correctly except that model object.
Model class :
class Model{
public static $db;
protected $_table_name;
public function __construct() {
self::$db =& DB::singleton();
}
public function get($rows="*",$where=null,$page_no=null,$limit=null,$order_by=null,$table_name=null){
if(!isset($table_name)||empty($table_name)){
self::$db->table=$this->_table_name;
}else{
self::$db->table=$table_name;
}
self::$db->rows = $rows;
self::$db->limit=$limit;
self::$db->where=$where;
self::$db->page_no=$page_no;
self::$db->order_by=$order_by;
return self::$db->read();
}
public function delete($id,$table){
self::$db->table = $table;
self::$db->where ="id='{$id}'";
self::$db->delete();
return 1;
}
public function drop_table($table){
self::$db->table=$table;
self::$db->drop_table();
return 1;
}
public function get_module_name(){
return $this->_table_name;
}
public function set_table($table_name){
$this->_table_name=$table_name;
}
}
Extended class :
class m_publication_categories extends model
{
protected $_table_name = 'publication_categories';
}
Controller :
class Controller{
public $db;
public $model;
public function __construct(){
core::$theme->setup();
$model_class_name = "m_".get_called_class();
$this->model=new $model_class_name();
}
public function listAction(){
$module_name = $this->model->get_module_name();
$module_model = new m_modules();
/**moduleinfo*/
$module = $module_model->get("*","table_name='{$module_name}'");
$module = $module[0];
$module_columns = $module_model->list_fields($module->id);
$data = $this->model->get();
print_r($data);die;
$this->assign('data',$data);
$this->assign('module_columns' , $module_columns);
$this->assign('module',$module);
$this->display('list_module_data','modules');
}
}
I want use model::get method from controller $this->model->get but that not work correctly. But if i print_r $this->model in controller , return result like that :
m_publication_categories Object ( [_table_name:protected] => publication_categories )
This means code in extends model class, but why not enter main model class ?
thanks.
I have an action in my controller called createAction. I also have a model My_Application_Product, that I'm using to create the product within the system. I'm following the Architecting Your Models talk. Is this the "correct" way to save my product? Code for My_Application_Product follows below.
class ProductController extends Zend_Controller_Action {
public function createAction() {
$categoryAdapter = new Application_Model_Categories();
$categories = $categoryAdapter->fetchAll('parent_id IS NOT NULL');
$form = new My_Application_Forms_Product_Create();
$category = $form->getElement('category');
foreach ($categories as $cat) {
$category->addMultiOption($cat->id, $cat->name);
}
if ($this->getRequest()->isPost()) {
if (! $form->isValid($_POST)) {
$this->view->form = $form;
return $this->render('create');
}
$product = new My_Application_Product();
$product->name = $_POST['name'];
$product->company_id = 1;
$product->category_id = $_POST['category'];
$product->trade_names = $_POST['trade_names'];
$product->website = $_POST['website'];
$product->description = $_POST['description'];
$product->closed_loop = $_POST['closed_loop'];
$product->sold_as = $_POST['sold_as'];
$product->sold_in = $_POST['sold_in'];
$product->dilution = $_POST['dilution'];
$id = $product->save();
$url = $this->getHelper('Url')
->url(array('action' => 'registryservices', 'id' => $id));
$this->_redirect($url);
}
$this->view->form = $form;
}
}
'
class My_Application_Product implements My_Application_Product_Interface {
// declare all the internally used variables here.
// if something isn't showing up when trying to save, that's probably
// because it's missing from here
protected $_id;
protected $_name;
protected $_company_id;
protected $_trade_names;
protected $_website;
protected $_description;
protected $_closed_loop;
protected $_sold_as;
protected $_sold_in;
protected $_dilution;
protected $_category_id;
protected $_verification_level;
protected $_dfe_sccp;
protected $_dfe_siicp;
protected $_el_ccd_hsc;
public function __set($name, $value) {
$local_var_name = "_" . $name;
if (property_exists($this, $local_var_name)) {
$this->{$local_var_name} = $value;
}
}
public function __get($name) {
$local_var_name = "_" . $name;
if (property_exists($this, $local_var_name)) {
return $this->{$local_var_name};
}
}
/**
*
* #param array $data The data to save
*/
public function save() {
// this means we're editing something
if ($this->id) {
$table = new My_Application_Product_Table();
$data = $table->find($this->id)->toArray();
$data = $data[0];
foreach (get_class_vars(get_class($this)) as $key => $value) {
if (! is_null($this->$key)) {
$data[preg_replace('/^_/', '', $key)] = $this->$key;
}
}
$id = $table->update($data, sprintf('id = %d', $this->id));
// this means we're creating, and this is the data we need
} else {
$data = array(
'id' => rand(1,1000000),
'name' => $this->name,
'date_created' => date('Y-m-d H:i:s'),
);
$id = $table->insert($data);
}
return $id;
}
}
'
class My_Application_Product_Table extends Zend_Db_Table_Abstract {
protected $_name = 'products';
protected $_primary = 'id';
}
Split your model in multiple classes :
1 class representing the entity (no methods, except for accessors).
this class represents your "real-life" object, and is just a structured data container, which encapsulates data
class My_Application_Model_Product {
protected $_id;
protected $_name;
protected $_company_id;
protected $_trade_names;
protected $_website;
//...
public function __set($name, $value) {
//Implement your setter here
}
public function __get($name) {
}
}
1 class responsible of data mapping.
This class makes is the link between your data source (database, webservice, file...) and your entity.
Class My_Application_Model_DataMapper_Product {
protected $_adapter
public function __construct($adapter)
{
$this->setAdapter($adapter);
}
public function setAdapter($adapter)
{
$this->_adapter = $adapter;
}
public function save(My_Application_Model_Product $product)
{
//Perform your save operation here
}
public function fetchAll()
{
}
public function findById($id)
{
}
//You may implement specific methods for any needed specific operation (search, bulk-update...
}
a third class for data access and persistence (Zend_Db_table, Soap client...) This third class is passed to the datamapper as the adapter and is used inside the methods to getch/save data.
With this architecture, you have a clear separation of responsibilities, and may change one part without affecting the other : for example, you could switch from a database to a webservice without affecting your Product class.
A very simple example is given in the zf Quickstart
Basically I have a class that sends a SOAP request for room information receives a response, it can only handle one room at a time.. eg:
class roomParser {
private $numRooms;
private $adults;
private $dailyPrice;
public function parse(){}
public function send(){}
};
$room = new roomParser( $arrival, $departue );
$return = $room->parse();
if ( $return ) { }
Now I have the dilemma of basically supporting multiple rooms, and for each room I have to separately keep information of the dailyPrice, # of adults, so I have to sessionize each rooms information since its a multiple step form..
Should I just create multiple instances of my object, or somehow modify my class so it supports any # of rooms in a rooms array, and in the rooms array it contains properties for each room?
Edit #1: After taking advice I tried implementing the Command pattern:
<?php
interface Parseable {
public function parse( $arr, $dept );
}
class Room implements Parseable {
protected $_adults;
protected $_kids;
protected $_startDate;
protected $_endDate;
protected $_hotelCode;
protected $_sessionNs;
protected $_minRate;
protected $_maxRate;
protected $_groupCode;
protected $_rateCode;
protected $_promoCode;
protected $_confCode;
protected $_currency = 'USD';
protected $_soapAction;
protected $_soapHeaders;
protected $_soapServer;
protected $_responseXml;
protected $_requestXml;
public function __construct( $startdate,$enddate,$rooms=1,$adults=2,$kids=0 ) {
$this->setNamespace(SESSION_NAME);
$this->verifyDates( $startdate, $enddate );
$this->_rooms= $rooms;
$this->_adults= $adults;
$this->_kids= $kids;
$this->setSoapAction();
$this->setRates();
}
public function parse( $arr, $dept ) {
$this->_price = $arr * $dept * rand();
return $this;
}
public function setNamespace( $namespace ) {
$this->_sessionNs = $namespace;
}
private function verifyDates( $startdate, $enddate ) {}
public function setSoapAction( $str= 'CheckAvailability' ) {
$this->_soapAction = $str;
}
public function setRates( $rates='' ) { }
private function getSoapHeader() {
return '<?xml version="1.0" encoding="utf-8"?>
<soap:Header>
</soap:Header>';
}
private function getSoapFooter() {
return '</soap:Envelope>';
}
private function getSource() {
return '<POS>
<Source><RequestorId ID="" ID_Context="" /></Source>
</POS>';
}
function requestXml() {
$this->_requestXml = $this->getSoapHeader();
$this->_requestXml .='<soap:Body></soap:Body>';
return $this->_requestXml;
}
private function setSoapHeaders ($contentLength) {
$this->_soapHeaders = array('POST /url HTTP/1.1',
'Host: '.SOAP_HOST,
'Content-Type: text/xml; charset=utf-8',
'Content-Length: '.$contentLength);
}
}
class RoomParser extends SplObjectStorage {
public function attach( Parseable $obj ) {
parent::attach( $obj );
}
public function parseRooms( $arr, $dept ) {
for ( $this->rewind(); $this->valid(); $this->next() ) {
$ret = $this->current()->parse( $arr, $dept );
echo $ret->getPrice(), PHP_EOL;
}
}
}
$arrive = '12/28/2010';
$depart = '01/02/2011';
$rooms = new RoomParser( $arrive, $depart);
$rooms->attach( new Room( '12/28/2010', '01/02/2011') );
$rooms->attach( new Room( '12/29/2010', '01/04/2011') );
echo $rooms->count(), ' Rooms', PHP_EOL;
Well what you've defined is an object that handles a single room, so naturally, if you wanted to handle multiple rooms, you should create an object that is simply a collection of these single-room objects.
If you intend to interact with your MultiRoomParser in the same way that you do your RoomParsers, this scenario may be a good candidate for the Composite Pattern. Basically, your MultiRoomParser would contain a collection of RoomParsers, and when you call a method such as parse() on your MultiRoomParser, it simply iterates through all RoomParsers in its collection and calls parse() on each element.
Given from the information in the question, I'd probably use a Command Pattern
All Rooms should implement a parse() command
interface Parseable
{
public function parse($arr, $dept);
}
A room instance could look like this
class Room implements Parseable
{
protected $_price;
protected $_adults;
public function parse($arr, $dept) {
// nonsense calculation, exchange with your parse logic
$this->_price = $arr * $dept * rand();
return $this;
}
public function getPrice()
{
return $this->_price;
}
}
To go through them, I'd add them to an Invoker that stores all rooms and knows how to invoke their parse() method and also knows what to do with the return from parse(), if necessary
class RoomParser extends SplObjectStorage
{
// makes sure we only have objects implementing parse() in store
public function attach(Parseable $obj)
{
parent::attach($obj);
}
// invoking all parse() methods in Rooms
public function parseRooms($arr, $dept)
{
for($this->rewind(); $this->valid(); $this->next()) {
$ret = $this->current()->parse($arr, $dept);
// do something with $ret
echo $ret->getPrice(), PHP_EOL;
}
}
// other methods
}
And then you could use it like this:
$parser = new RoomParser;
$parser->attach(new Room);
$parser->attach(new Room);
$parser->attach(new Room);
$parser->attach(new Room);
echo $parser->count(), ' Rooms', PHP_EOL;
$parser->parseRooms(1,2);
Note that the Invoker extends SplObjectStorage, so it implements Countable, Iterator, Traversable, Serializable and ArrayAccess.
I would say that making multiple instances of the object makes sense. It's how the objects works.