Our distributed database have php client which is developed on php extension.
We store our database object in persistant_list.
Problem:
From the log we find, for the same process, sometimes it can't find the database object from persistent_list, have to init the db object in persistant_list, but later(probably 1s)it can't find the same key-value again. It seems the value in persistent list is destroyed. Based on my poor knowledge about php, the values in persistent_list only destroyed by zend_hash_del or web server down. Source code:
if (zend_hash_find(&EG(persistent_list), hash_key, hash_key_len+1, (void **) &le) == FAILURE) {
tc = tair_init();
last_rst = tair_startup(tc,uri);
if(last_rst != TAIR_RETURN_SUCCESS){
return -1;
}
zend_rsrc_list_entry new_le;
new_le.type = le_tair;
new_le.ptr = tc;
/* register new persistent connection */
if (zend_hash_update(&EG(persistent_list), hash_key, hash_key_len+1, (void *) &new_le, sizeof(zend_rsrc_list_entry), NULL) == FAILURE) {
tair_deinit(tc);
tc = NULL;
} else {
rsrc_id = zend_list_insert(tc,le_tair);
}
}else if (le->type != le_tair || le->ptr == NULL) {
zend_hash_del(&EG(persistent_list), hash_key, hash_key_len+1);
tc = tair_init();
last_rst = tair_startup(tc,uri);
if(last_rst != TAIR_RETURN_SUCCESS){
return -1;
}
zend_rsrc_list_entry new_le;
new_le.type = le_tair;
new_le.ptr = tc;
if (zend_hash_update(&EG(persistent_list), hash_key, hash_key_len+1, (void *) &new_le, sizeof(zend_rsrc_list_entry), NULL) == FAILURE) {
tair_deinit(tc);
tc = NULL;
} else {
rsrc_id = zend_list_insert(tc,le_tair);
}
}else {
tc = (tair_handler)le->ptr;
rsrc_id = zend_list_insert(tc,le_tair);
}
PHP_MINIT_FUNCTION(tair)
{
REGISTER_INI_ENTRIES();
tair_set_loglevel(tair_globals.log_level);
le_tair = zend_register_list_destructors_ex(NULL,tair_dtor,"Tair session", module_number);
return SUCCESS;
}
Can anyone tell me what's wrong with my php zend engine? Btw the client side use Nginx+fpm.
Related
My application is executing user-defined SQL statements that contain query parameters. To detect the parameter names that should be passed to oci_bind_by_name I use a simple reg-ex pattern like /:\w+/ but this fails for string literals and comments contained in the SQL statement.
BEGIN
/* some unused :param here */
SELECT 'some other :param there' FROM foo;
END;
Handling string literal detection and comment by more reg-ex patterns seems like a bad idea when thinking about even more nasty examples like:
BEGIN
SELECT '/* some comment :literals --' FROM foo;
-- some more comment :literals */
END;
Is there some way to get the required query parameter names for binding using OCI8 functions? What other possibilities do exist without falling back to manually parsing SQL in user code?
My code below is not a great way to solve this problem. Before you use that code, keep looking for a more official solution.
It appears that OCI does have functionality to dynamically retrieve bind names, through the function OCIStmtGetBindInfo. However, it also looks like that function is not available in the default PHP functions. Maybe there are other, more advanced ways of connecting PHP to Oracle that supply the necessary function, but I don't know enough about OCI or PHP to find them.
If you're ready for a not-so-great solution, you can use my open source program plsql_lexer to find the bind variable names. The lexer breaks SQL statements into small tokens, and handles difficult syntax issues like comments and strings. The results should be much more accurate than using a few regular expressions.
The downside is that the program is not a full parser, and you have to deal with the primitive tokens. In this case, it's relatively easy to find 99.9999% of the bind variables with a single SQL statement. After installing the program, put your SQL into the middle of the following SELECT statement:
--Find bind variables.
--(Words or numerics that were immediately preceded (excluding whitespace) by a colon.)
select to_char(value) bind_variable_name
from
(
--Get previous token.
select type, value, first_char_position,
lag(to_char(type)) over (order by first_char_position) previous_type
from
(
--Convert to tokens, ignore whitespace.
select type, value, first_char_position
from table(plsql_lexer.lex(
q'[
--Here's the actual SQL statement you care about.
--/*:fake_bind1*/
select 1 a
from dual
where 1 = : real_bind_1 and :real_bind_2 = ':fake_bind_2'
]'))
where type not in ('whitespace')
order by first_char_position
)
)
where type in ('numeric', 'word')
and previous_type = ':'
order by first_char_position;
BIND_VARIABLE_NAME
------------------
real_bind_1
real_bind_2
There may still be some weird cases this code doesn't handle. For example, a bind variable can be a quoted identifier, you may need to handle the double quotes. And the above code doesn't handle indicators. On the other hand, I have literally never seen either of those features used, so it may not matter to you. Test thoroughly.
Finally I wrote some small state machine for parsing SQL statement bind parameters and put it into a helper class to not conflict with other globals:
class SqlBindNames {
private static function isLineBreak($ch) {
return (($ch === "\r") || ($ch === "\n"));
}
private static function isIdentChar($ch) {
return (($ch >= 'a') && ($ch <= 'z')) ||
(($ch >= 'A') && ($ch <= 'Z')) ||
(($ch >= '0') && ($ch <= '9')) ||
($ch === '_');
}
private const QUOTE_SINGLE_CHR = '\'';
private const QUOTE_DOUBLE_CHR = '"';
private const COMMENT_LINE_STR = "--";
private const COMMENT_BEGIN_STR = "/*";
private const COMMENT_END_STR = "*/";
private const BIND_START_CHR = ':';
private const MODE_NORMAL = 0;
private const MODE_QUOTE_SINGLE = 1;
private const MODE_QUOTE_DOUBLE = 2;
private const MODE_COMMENT_LINE = 3;
private const MODE_COMMENT_MULTI = 4;
private const MODE_BIND_VARNAME = 5;
public static function getSqlBindNames(string $sql, bool $unique = true) {
$mode = self::MODE_NORMAL;
$names = array();
$namesIndex = array();
$len = strlen($sql);
$i = 0;
while ($i < $len) {
$curr = $sql[$i];
if ($i < $len - 1) {
$next = $sql[$i + 1];
} else {
$next = "\0";
}
$nextMode = $mode;
if ($mode === self::MODE_NORMAL) {
if ($curr === self::QUOTE_SINGLE_CHR) {
$nextMode = self::MODE_QUOTE_SINGLE;
} else if ($curr === self::QUOTE_DOUBLE_CHR) {
$nextMode = self::MODE_QUOTE_DOUBLE;
} else if (($curr === self::COMMENT_LINE_STR[0]) && ($next === self::COMMENT_LINE_STR[1])) {
$i += 1;
$nextMode = self::MODE_COMMENT_LINE;
} else if (($curr === self::COMMENT_BEGIN_STR[0]) && ($next === self::COMMENT_BEGIN_STR[1])) {
$i += 1;
$nextMode = self::MODE_COMMENT_MULTI;
} else if (($curr === self::BIND_START_CHR) && self::isIdentChar($next)) {
$bindName = "";
$nextMode = self::MODE_BIND_VARNAME;
}
} else if (($mode === self::MODE_QUOTE_SINGLE) && ($curr === self::QUOTE_SINGLE_CHR)) {
$nextMode = self::MODE_NORMAL;
} else if (($mode === self::MODE_QUOTE_DOUBLE) && ($curr === self::QUOTE_DOUBLE_CHR)) {
$nextMode = self::MODE_NORMAL;
} else if (($mode === self::MODE_COMMENT_LINE) && self::isLineBreak($curr)) {
$nextMode = self::MODE_NORMAL;
} else if (($mode === self::MODE_COMMENT_MULTI) && ($curr === self::COMMENT_END_STR[0]) && ($next === self::COMMENT_END_STR[1])) {
$i += 1;
$nextMode = self::MODE_NORMAL;
} else if ($mode === self::MODE_BIND_VARNAME) {
if (self::isIdentChar($curr)) {
$bindName = $bindName . $curr;
}
if (!self::isIdentChar($next)) {
/* found new bind param */
if (!$unique || !in_array(strtolower($bindName), $namesIndex)) {
array_push($namesIndex, strtolower($bindName));
array_push($names, $bindName);
}
$nextMode = self::MODE_NORMAL;
}
}
$i += 1;
$mode = $nextMode;
}
return $names;
}
}
It seems to work, improvements are welcome!
I have a function to check wether a function/module is disabled by an admin or not.
The function looks like this (in functions.php):
function function_enabled($function_module) {
include("dbconnect.php");
if ($function_module == "changelogs" OR $function_module == "news" OR $function_module == "calendar" OR $function_module == "groups" OR $function_module == "settings" OR $function_module == "eat" OR $function_module == "weather" OR $function_module == "usertext" OR $function_module == "login") {
$sql = $db->query("SELECT enabled FROM functions WHERE function_name = '$function_module'");
$row = $sql->fetch_object();
$return = $row->enabled;
return $return;
}
}
And this is how I use the function (in any other php file):
include("functions.php");
if(function_enabled("weather") == 1) {
//do the weather stuff here
} else {
echo"Function disabled";
}
Now my question: How is the performance of this function? Can this cause relative high performance hits? Is there a better/faster way to check wether a function is enabled or not or is this script okay?
UPDATE 1
Now, I combined these multiple queries into one so that all values are in an array.
function compare_id($a, $b) {
if ($a['id'] == $b['id']) return 0;
return ($a['id'] < $b['id']) ? -1 : 1;
}
$sql = $db->query("SELECT function_name, enabled, id FROM functions");
$function_array = array();
while ($row = mysqli_fetch_assoc($sql)) {
$function_array[] = $row;
}
usort($function_array, compare_id);
if($function_array[0]['enabled'] == 1) {
echo"changelogs enabled<br>";
} else {
echo"changelogs disabled<br>";
}
if($function_array[2]['enabled'] == 1) {
echo"calendar enabled<br>";
} else {
echo"calendar disabled<br>";
}
if($function_array[7]['enabled'] == 1) {
echo"usertext enabled<br>";
} else {
echo"usertext disabled<br>";
}
if($function_array[8]['enabled'] == 1) {
echo"login enabled<br>";
} else {
echo"login disabled<br>";
}
if($function_array[4]['enabled'] == 1) {
echo"settings enabled<br>";
} else {
echo"settings disabled<br>";
}
Is this method faster?
It looks OK. Is it giving you issues? There could be performance issues in the DB query from the table or index.
Generally you shouldn't build for performance from the start. Rather build something that functions, then go back an optmise where the choke points are. There are high load webservices which use simple frameworks (Ruby on Rails, Laravel) They only have to optimise 2-5% of the code where the load is. When you know where the choke points are and why its choking you can make an educated choice on how to optmise. Should it be optimised, or should I rewite this in another language.
Does your functions table change fequently? If not you could possibly export it to file. Then PHP could read it once and store it in Memory as an array. Referencing an array would be a lot faster functions['weather'] == 1 with no DB throughput issues.
function function_enabled($function_module) {
$_FUNCTION_MODULE = array ("changelogs", "news", "calendar", "groups", "settings", "eat", "weather", "usertext", "login");
require_once ("dbconnect.php");
if (in_array($function_module, $_FUNCTION_MODULE) {
$query = $db->query("SELECT enabled FROM functions WHERE function_name = '$function_module'");
$row = $query->fetch_object();
$isEnabled = $row->enabled;
return $isEnabled;
}
return NULL;
}
This question already has answers here:
Pass Javascript Array -> PHP
(5 answers)
Closed 9 years ago.
k, i got that so i checked again and found interesting thing that i was missing:
javascript:
function my_tmpDropFunc(ddObj, targetObj ,doll)
{
if(isDropTarget(targetObj, ddObj) || confirmed)
{
var typeTarget = getPositionType(targetObj.id);
var typeDD = getPositionType(ddObj.id);
// Item-Types to Confirm using
toConfirm = new Array();
//toConfirm.push(new Array(32768, 'confirmCostume'));
//Itembox
if (typeTarget == 384)
{
onDropOnItembox(ddObj, targetObj)
return "break";
}
if (typeDD == 384 && onDropFromItembox(ddObj, targetObj))
return "break";
//Essen
if(typeTarget == 8)
{
ddObj.moveTo(pickObj.x, pickObj.y);
SwapBlockingfields(false);
changeShow();
SetToolTip(true);
PICK_ITEM = false;
// Gurt, Style
if(ddObj.contenttype == 64 || ddObj.contenttype == 32768)
{
// If a Confirmation needed, not confirmed an the type requires a confirmation...
if (confirmNeeded && !confirmed && in_multi_array(toConfirm, ddObj.contenttype))
{
// Get the correct BlackOutDialogBox (Defined in array).
for (key in toConfirm)
{
if (ddObj.contenttype == toConfirm[key][0])
{
blackOutDialogBoxToUse = toConfirm[key][1];
if (showUseItemAlarm(ddObj,targetObj,typeTarget,doll,typeDD,blackOutDialogBoxToUse))
return "return";
}
}
}
//Verbrauchen
dd.elements[ddObj.name].hide();
targetObj.div.style.cursor = 'wait';
changeDraggable(false);
ddObj.moveTo(pickObj.x, pickObj.y);
doll = document.getElementById('plDoll').value;
sendRequest('get', 'ajax.php', 'mod=overview&submod=useItem&item='+ ddObj.id +'&doll='+doll);
}
return "return";
}
//Crafting
if (checkCrafting(ddObj,targetObj,typeDD,typeTarget))
return "return";
//Enchanten
if (targetObj.iid && typeTarget < 256 && !ddObj.enchanttype)
return "continue";
if (checkEnchant(ddObj, targetObj, typeTarget))
return "return";
//Ins Inventar
if (typeTarget >= 512)
{
x = getPositionX(targetObj.id)-1;
y = getPositionY(targetObj.id)-1;
field = CalcFieldWithObj(ddObj, typeTarget, maxcols, maxrows);
if(!(field[x][y] & ddObj.contentsize))
return "continue";
}
if(!(targetObj.contenttypeaccept & ddObj.contenttype))
return "continue";
//Bestätigung bevor Item Seelengebunden wird.
if (showUniqueItemAlarm(ddObj,targetObj,typeTarget,doll,typeDD))
return "return";
my_SwapContent (targetObj,ddObj);
newXY(targetObj);
newXY(ddObj);
if(typeTarget < 256)
reformEquiped(targetObj, targetObj.w, targetObj.h, true);
else if(typeDD < 256)
{
reformEquiped(ddObj,targetObj.w, targetObj.h, false);
ddObj.contentsize = ddObj.contentsizebase;
newXY(dd.obj);
}
targetObj.maximizeZ();
//aElts[i].div.style.zindex = 200;
// Pruefen ob von Lager verschoben wird
var from = ddObj.id.substring(1, 4);
var storage = false;
if (from >= 352 && from <= 357)
storage = true;
if (storage)
sendRequest('get', 'ajax/guildstorageswap.php', 'old='+ ddObj.id +'&new='+targetObj.id+'&doll='+doll);
else
sendRequest('get', 'ajax/inventoryswap.php', 'old='+ ddObj.id +'&new='+targetObj.id+'&doll='+doll);
tt_Init(1);
return "break";
}
}
as you can see like "Reflic" suggested it sends data to ajax, so in ajax.php what do i need to create to receive that data what does it send?
Look into json_encode. Example:
var myJSvariable = <?php echo json_encode($myPHPvariable); ?>;
This works for any type of PHP variable, excluding Resources.
Use AJAX to send Requests via JavaScript to an PHP Script.
http://api.jquery.com/category/ajax/ - http://api.jquery.com/jQuery.ajax/
Edit: And of course use the JSON Format.
I am creating a PHP extension in C to access the SPI interface. So far I have gotten pretty much everything working: php_spi on Github
However, I cannot seem to make the $options parameter in the constructor optional. My working code is like this:
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lla", &bus, &chipselect, &options) == FAILURE) {
return;
}
_this_zval = getThis();
_this_ce = Z_OBJCE_P(_this_zval);
options_hash = HASH_OF(options);
char device[32];
sprintf(device, "/dev/spidev%d.%d", bus, chipselect);
// If the device doesn't exists, error!
if(access(device, F_OK) == -1) {
char error[128];
sprintf(error, "The device %s does not exist", device);
php_error(E_ERROR, error);
}
// If we can't open it, error!
long fd = open(device, O_RDWR);
if (fd < 0) {
char error[128];
sprintf(error, "Could not open %s for read/write operations, are you running as root?", device);
php_error(E_ERROR, error);
}
// Set the file descriptor as a class property
zend_update_property_long(_this_ce, _this_zval, "device", 6, fd TSRMLS_DC);
// Default property values
uint8_t mode = SPI_MODE_0;
uint8_t bits = 8;
uint32_t speed = 500000;
uint16_t delay = 0;
// Loop through the options array
zval **data;
for(zend_hash_internal_pointer_reset(options_hash);
zend_hash_get_current_data(options_hash, (void **)&data) == SUCCESS;
zend_hash_move_forward(options_hash)) {
char *key;
int len;
long index;
long value = Z_LVAL_PP(data);
if(zend_hash_get_current_key_ex(options_hash, &key, &len, &index, 1, NULL) == HASH_KEY_IS_STRING) {
// Assign the value accordingly
if(strncmp("mode", key, len) == 0) {
switch(value) {
case SPI_MODE_1:
mode = SPI_MODE_1;
break;
case SPI_MODE_2:
mode = SPI_MODE_2;
break;
case SPI_MODE_3:
mode = SPI_MODE_3;
break;
default:
mode = SPI_MODE_0;
break;
}
}
else if(strncmp("bits", key, len) == 0) {
bits = value;
}
else if(strncmp("speed", key, len) == 0) {
speed = value;
}
else if(strncmp("delay", key, len) == 0) {
delay = value;
}
}
}
However, if I follow the suggestions of all the documentation I can find and add a pipe between the l and the a, like so:
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll|a", &bus, &chipselect, &options) == FAILURE) {
Then my extension silently fails - can anyone offer me any advice?
Assuming options is a zval*, if you do this:
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll|a", &bus, &chipselect, &options) == FAILURE) {
return;
}
...if options is not passed (that is, you omit the third optional argument), options will not be initialized or modified. Later, you do this:
options_hash = HASH_OF(options);
Therefore, you're using either an uninitialized pointer, or a NULL pointer, which is undefined behavior. This is most likely causing a segmentation fault, causing your PHP script to fail.
What you should do is something like:
zval* options = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll|a", &bus, &chipselect, &options) == FAILURE) {
return;
}
// ...
if (options != NULL) {
options_hash = HASH_OF(options);
}
...and handle every instance of options (and options_hash) with a condition that checks if it's NULL or not.
I am working with SilverStripe, and I am working on making a newspage.
I use the DataObjectAsPage Module( http://www.ssbits.com/tutorials/2012/dataobject-as-pages-the-module/ ), I got it working when I use the admin to publish newsitems.
Now I want to use the DataObjectManager Module instead of the admin module to manage my news items. But this is where the problem exists. Everything works fine in draft mode, I can make a new newsitem and it shows up in draft. But when I want to publish a newsitem, it won't show up in the live or published mode.
I'm using the following tables:
-Dataobjectaspage table,
-Dataobjectaspage_live table,
-NewsArticle table,
-NewsArticle_Live table
The Articles have been inserted while publishing in the Dataobjectaspage table and in the NewsArticle table... But not in the _Live tables...
Seems the doPublish() function hasn't been used while 'Publishing'.
So I'm trying the use the following:
function onAfterWrite() {
parent::onAfterWrite();
DataObjectAsPage::doPublish();
}
But when I use this, it gets an error:
here is this picture
It seems to be in a loop....
I've got the NewsArticle.php file where I use this function:
function onAfterWrite() {
parent::onAfterWrite();
DataObjectAsPage::doPublish();
}
This function calls the DataObjectAsPage.php file and uses this code:
function doPublish() {
if (!$this->canPublish()) return false;
$original = Versioned::get_one_by_stage("DataObjectAsPage", "Live", "\"DataObjectAsPage\".\"ID\" = $this->ID");
if(!$original) $original = new DataObjectAsPage();
// Handle activities undertaken by decorators
$this->invokeWithExtensions('onBeforePublish', $original);
$this->Status = "Published";
//$this->PublishedByID = Member::currentUser()->ID;
$this->write();
$this->publish("Stage", "Live");
// Handle activities undertaken by decorators
$this->invokeWithExtensions('onAfterPublish', $original);
return true;
}
And then it goes to DataObject.php file and uses the write function ():
public function write($showDebug = false, $forceInsert = false, $forceWrite = false, $writeComponents = false) {
$firstWrite = false;
$this->brokenOnWrite = true;
$isNewRecord = false;
if(self::get_validation_enabled()) {
$valid = $this->validate();
if(!$valid->valid()) {
// Used by DODs to clean up after themselves, eg, Versioned
$this->extend('onAfterSkippedWrite');
throw new ValidationException($valid, "Validation error writing a $this->class object: " . $valid->message() . ". Object not written.", E_USER_WARNING);
return false;
}
}
$this->onBeforeWrite();
if($this->brokenOnWrite) {
user_error("$this->class has a broken onBeforeWrite() function. Make sure that you call parent::onBeforeWrite().", E_USER_ERROR);
}
// New record = everything has changed
if(($this->ID && is_numeric($this->ID)) && !$forceInsert) {
$dbCommand = 'update';
// Update the changed array with references to changed obj-fields
foreach($this->record as $k => $v) {
if(is_object($v) && method_exists($v, 'isChanged') && $v->isChanged()) {
$this->changed[$k] = true;
}
}
} else{
$dbCommand = 'insert';
$this->changed = array();
foreach($this->record as $k => $v) {
$this->changed[$k] = 2;
}
$firstWrite = true;
}
// No changes made
if($this->changed) {
foreach($this->getClassAncestry() as $ancestor) {
if(self::has_own_table($ancestor))
$ancestry[] = $ancestor;
}
// Look for some changes to make
if(!$forceInsert) unset($this->changed['ID']);
$hasChanges = false;
foreach($this->changed as $fieldName => $changed) {
if($changed) {
$hasChanges = true;
break;
}
}
if($hasChanges || $forceWrite || !$this->record['ID']) {
// New records have their insert into the base data table done first, so that they can pass the
// generated primary key on to the rest of the manipulation
$baseTable = $ancestry[0];
if((!isset($this->record['ID']) || !$this->record['ID']) && isset($ancestry[0])) {
DB::query("INSERT INTO \"{$baseTable}\" (\"Created\") VALUES (" . DB::getConn()->now() . ")");
$this->record['ID'] = DB::getGeneratedID($baseTable);
$this->changed['ID'] = 2;
$isNewRecord = true;
}
// Divvy up field saving into a number of database manipulations
$manipulation = array();
if(isset($ancestry) && is_array($ancestry)) {
foreach($ancestry as $idx => $class) {
$classSingleton = singleton($class);
foreach($this->record as $fieldName => $fieldValue) {
if(isset($this->changed[$fieldName]) && $this->changed[$fieldName] && $fieldType = $classSingleton->hasOwnTableDatabaseField($fieldName)) {
$fieldObj = $this->dbObject($fieldName);
if(!isset($manipulation[$class])) $manipulation[$class] = array();
// if database column doesn't correlate to a DBField instance...
if(!$fieldObj) {
$fieldObj = DBField::create('Varchar', $this->record[$fieldName], $fieldName);
}
// Both CompositeDBFields and regular fields need to be repopulated
$fieldObj->setValue($this->record[$fieldName], $this->record);
if($class != $baseTable || $fieldName!='ID')
$fieldObj->writeToManipulation($manipulation[$class]);
}
}
// Add the class name to the base object
if($idx == 0) {
$manipulation[$class]['fields']["LastEdited"] = "'".SS_Datetime::now()->Rfc2822()."'";
if($dbCommand == 'insert') {
$manipulation[$class]['fields']["Created"] = "'".SS_Datetime::now()->Rfc2822()."'";
//echo "<li>$this->class - " .get_class($this);
$manipulation[$class]['fields']["ClassName"] = "'$this->class'";
}
}
// In cases where there are no fields, this 'stub' will get picked up on
if(self::has_own_table($class)) {
$manipulation[$class]['command'] = $dbCommand;
$manipulation[$class]['id'] = $this->record['ID'];
} else {
unset($manipulation[$class]);
}
}
}
$this->extend('augmentWrite', $manipulation);
// New records have their insert into the base data table done first, so that they can pass the
// generated ID on to the rest of the manipulation
if(isset($isNewRecord) && $isNewRecord && isset($manipulation[$baseTable])) {
$manipulation[$baseTable]['command'] = 'update';
}
DB::manipulate($manipulation);
if(isset($isNewRecord) && $isNewRecord) {
DataObjectLog::addedObject($this);
} else {
DataObjectLog::changedObject($this);
}
$this->onAfterWrite();
$this->changed = null;
} elseif ( $showDebug ) {
echo "<b>Debug:</b> no changes for DataObject<br />";
// Used by DODs to clean up after themselves, eg, Versioned
$this->extend('onAfterSkippedWrite');
}
// Clears the cache for this object so get_one returns the correct object.
$this->flushCache();
if(!isset($this->record['Created'])) {
$this->record['Created'] = SS_Datetime::now()->Rfc2822();
}
$this->record['LastEdited'] = SS_Datetime::now()->Rfc2822();
} else {
// Used by DODs to clean up after themselves, eg, Versioned
$this->extend('onAfterSkippedWrite');
}
// Write ComponentSets as necessary
if($writeComponents) {
$this->writeComponents(true);
}
return $this->record['ID'];
}
Look at the $this->onAfterWrite();
It probably goes to my own function on NewsArticle.php and there starts the loop! I'm not sure though, so i could need some help!!
Does anyone knows how to use the doPublish() function?
The reason that is happening is that in the DataObjectAsPage::publish() method, it is calling ->write() - line 11 of your 3rd code sample.
So what happens is it calls ->write(), at the end of ->write() your onAfterWrite() method is called, which calls publish(), which calls write() again.
If you remove the onAfterWrite() function that you've added, it should work as expected.
The doPublish() method on DataObjectAsPage will take care of publishing from Stage to Live for you.