Archive

Posts Tagged ‘php’

Porque PHP 7 y no PHP 6?

December 8, 2014 Leave a comment

Para los que se interesan en el progreso de PHP como lenguaje, y en la integración de funcionalidades para optimizar el lenguaje y su uso, se habrán percatado que la próxima versión de PHP se está siendo llamar PHP 7, y no PHP 6.

Porque? Bueno, hay muchas razones, pero la conclusión es que PHP 6 fue un intento algo descoordinado y fallado de la comunidad de desarrolladores de PHP (como el mismo número de versión lo fue para otros software), y que usar 6 confundiría la gente, ya que al final se abandonó lo que se esperaba de la versión 6 y se integró poco a poco en 5.3, 5.4, 5.5 y 5.6.

Si quieren conocer los detalles, se ha elaborado un documento extenso que ha llevado a un voto de parte de los desarrolladores, a favor (en la mayoría) de la versión 7, aquí: https://wiki.php.net/rfc/php6

Categories: php, PHP Perú, Spanish Tags: ,

Guía de puntos críticos al actualizar PHP

October 18, 2014 Leave a comment

Si, como nosotros en BeezNest, con Chamilo, tiene que tomar a cargo el mantenimiento de una aplicación PHP masiva (varios cientos de miles de líneas) en el tiempo, esta guía le será útil.

En muchas oportunidades, tendrá que asegurar que su sistema soporte actualizaciones de PHP, y también que soporte varias versiones al mismo tiempo, según los casos presentados por sus clientes.

Habiendo desarrollado una aplicación años atrás, con las tecnologías del momento, tendrá que modificar el código para asegurar el soporte de cada nueva versión de PHP. En esto no solo tendrá que actualizar el código que usa cosas abandonadas (deprecated) en la siguiente versión de PHP, sino también tendrá, si desea asegurar el soporte de versiones anteriores, que asegurar que su sistema funcione con la menor versión posible de PHP.

Ante todo, es importante ubicar los cambios notables entre versiones de PHP. Para ello, puede consultar la página de Changelog de PHP 5 si desea todos los detalles, o la página de apéndice con los procedimientos de actualización de una versión a otra de PHP si prefiere una versión más explicada.

Si bien es cierto no todas las versiones de PHP fueron de la 5, es importante entender que hoy en día la versión 4 de PHP es muy insuficiente para hacer cualquier aplicación compleja, y probablemente no esté soportada por la mayoría de servidores existentes, pero también podemos estar relativamente tranquilos mirando hacia el futuro: si bien existieron grandes planes para PHP 6 (con un gran enfoque en la internacionalización) hacen unos años, estos planes se quedaron dormidos por ahora y la comunidad de desarrolladores de PHP se está principalmente enfocando en nuevas versiones de PHP 5.*.

La lista que les entrego aquí es una lista *resumida* proviniente de los procedimientos de actualización en la página indicada arriba, y que considero una lista suficiente para el 90% de las aplicaciones.

De PHP 5.2 a 5.3

  • Aparición de los namespaces
  • Aparición de los closures (funciones sin nombre)
  • Aparición del operador ternario ( COND ? A : B; )
  • Aparición del acceso dinámico a clases estáticas tipo $clase::$prop
  • Aparición de excepciones anidadas
  • Garbage colector cíclico (no implica ningún cambio pero reduce las fugas de memoria con objetos que contienen objetos)

De PHP 5.3 a 5.4

  • Aparición de traits
  • Habilitación de $this en los closures
  • Aparición de forma corta de definición de arrays tipo $a = [1, 2, 3];
  • Aparición de la dereferencia de arrays, tipo foo()[0]
  • Habilitación automática de la posibilidad de usar la forma corta de tag PHP “<?=”
  • Habilitación de la posibilidad de usar la forma corta (new Foo)->bar()
  • Mejora (o sea… cambio) en los mensajes de errores (ayudando al debug de aplicaciones PHP)
  • Habilitación general de la funcionalidad de progreso de subida (upload progress) que anteriormente requería un módulo de PEAR. Esto es lo que permite mostrar, en una página en PHP con JS, una barra de progreso mientras el archivo está siendo subido.

De PHP 5.4 a 5.5

  • Aparición de los generators
  • Habilitación de la forma: foreach ($array as list($ a, $b))
  • Habilitación de la dereferenciación para strings y arrays: echo [1, 2, 3][0]; / echo ‘PHP'[0];
  • Inclusión fácil de Zend OPCache (Zend Optimizer Plus), permitiendo un remplazo de APC, que definitivamente generaba un montón de inconsistencias con sistemas complejos como Drupal y WordPress, entre otros, con más estabilidad y un poco más de eficiencia (ver el Benchmark de Zend OPCode)
  • Inclusión del formato WebP (la versión imágenes del codec WebM de Google) en GD

De PHP 5.5 a 5.6

  • Aparición de Variadic, un mecanismo para declarar una función con una cantidad de parámetros variable
  • Escaladores de constantes, tipo TWO = ONE*2
  • Aparición del operador de exponente (antes “pow()”) como “a**b” (2**3 = 8)
  • Aparición del stream php://input
  • Habilitación del soporte de archivos de tamaño superior a 2GB (large files uploads)
  • Mejoras importantes de seguridad dentro de la librería mcrypt.

Es importante entender que, si bien todas estas mejoras son buenas para PHP y sus aplicaciones, solo podrá usarlas si “abandona” el soporte para una versión anterior de PHP.

Por ejemplo, si quiere usar Zend OPCache dentro de su aplicación como elemento obligatorio, su aplicación deberá indicar muy claramente que funciona con versiones de PHP “a partir de PHP 5.5”.

Existen mecanismos alternativos, a veces, que permiten ofrecer un soporte de versiones anteriores y, si alguna funcionalidad más eficiente está presente, usarla, pero en la práctica muchas no lo permiten. Por lo tanto, ofrecer el soporte para versiones anteriores de PHP también implica menor eficiencia para aplicaciones grandes en ambientes muy exigentes.

Chamilo

Chamilo LMS es un sistema de e-learning de software libre, que tiene como objetivo ofrecer herramientas potentes para la mejora de la educación y de la disponibilidad de esta para toda la humanidad. Sus características principales son: extrema usabilidad y poco uso de recursos. Puede descargar Chamilo desde su sitio web: http://www.chamilo.org o probarlo gratuitamente en https://campus.chamilo.org.

BeezNest

BeezNest es una organización internacional (be, es, fr, de, uk, pe, mx) creada en el 2002, que desarrolla el software Chamilo LMS y se especializa en ofrecer toda la gama de servicios especializados (consultoría, implementación, instalación, modificación, capacitación, alojamiento, etc) sobre este software, y optimización de servidores web que permiten alcanzar mayor productividad con sus aplicaciones web en PHP.

Can’t use function return value in write context

September 26, 2014 Leave a comment

Did you ever develop some nice code, then simply wanted to check if a string was only composed of white spaces or tabs, and used something like this:

if (!empty(trim($string))) { ... }

…only to get a bad error appear (only on your PHP 5.4 server) in the middle of your app, like this?:

Fatal error: Can't use function return value in write context

Well, this has a simple explanation…

Prior to PHP 5.5, the empty() function did not accept something else than a variable. From php.net/empty:

Note:

Prior to PHP 5.5, empty() only supports variables; anything else will result in a parse error. In other words, the following will not work: empty(trim($name)). Instead, use trim($name) == false.

So the easy solution (but kind of making PHP feel beyond help) is to do what is recommended in the comment (trim($string) == false) or decompose your statement (although it will be less efficient than the above):

$string = trim($string);
if (!empty($string)) { ... }

This is due to the fact that empty() is a language construct, not really a function (it works as a function to make things easier). So, under the same logic, this will not work with other language constructs, like echo, require, etc (although the explanation in empty’s documentation is clearer than any other).

Categories: Development, English, php Tags:

Using php5-memcached to store sessions on distributed servers

This article is an extension of the previous article about storing sessions in Memcached with PHP.

Using Memcached for sessions storage is generally a matter of speed (storing them in memory is faster than on disk) and of scalability (using a Memcached server allows you to have several web servers serving the same PHP application in a seamless way).

In the previous article, we mentioned (3 years ago) that the php5-memcached extension did not seem to manage the storage of sessions quite well, or at least that the configuration of such setup was not well documented. This time, we’ve been able to do it with php5-memcached. This time also, we’re talking about distributing Memcached on several servers. Typically, in a cloud environment with several web servers and no dedicated memcached server, or a need for some kind of fail-over solution for the memcached server. We’re setting this up on 3 Ubuntu 14.04 servers in 64bit that act as load-balanced web servers behind a load balancer managed by Nginx. If you use another distribution or operating system, you might need to adapt the commands we’re showing here.

Installing Memcached + PHP

So the few commands you need to launch first are to install the required software, on the 3 web servers (you can either do that on one of them then replicate the image if you are using Cloud instances, or you can install simultaneously on your 3 web servers using ClusterSSH, for example, with the cssh server1 server2 server3 command) :

sudo apt-get install memcached php5-memcached
sudo service apache2 restart

The second command is to make sure Apache understands the php5-memcached extension is there.

In order to enable the connection to Memcached from the different load-balanced servers, we need to change the Memcached configuration to listen on the external IP address. Check the IP address with /sbin/ifconfig. The Memcached configuration file is located in /etc/memcached.conf. Locate the “-l” option and change “127.0.0.1” for your external IP, then save and close the file. Note that this might introduce a security flaw, where you are possibly opening the connection to your Memcached server to the outside world. You can prevent that using a firewall (iptables is a big classic, available on Ubuntu)

Next, restart the Memcached daemon:

sudo service memcached restart

Now you have a Memcached server running on each of your web servers, accessible from the other web servers. To test this, connect to any of your web servers and try a telnet connection on the default Memcached port: 11211, like so:

user@server1$ telnet ip-server2 11211

To get out of there, just type “quit” and Enter.

OK, so now we have 3 Memcached servers, we only need to wrap up configuring PHP to use these Memcached servers to store sessions.

Configuring PHP to use Memcached as session storage

This is done by editing your Apache VirtualHost files (on each web server) and adding (before the closing </VirtualHost> tag) the following PHP settings:

 php_admin_value session.save_handler memcached
 php_admin_value session.save_path "ip-server1:11211,ip-server2:11211,ip-server3:11211"

Now reload your web server:

 sudo service apache2 reload

You should now be able to connect to your web application using the distributed Memcached server as a session storage (you usually don’t need to change anything in your application itself, but some might exceptionally define their own session storage policy).

The dangers of using a distributed Memcached server

Apart from the possible open access to your Memcached server previously mentioned, which is particularly security-related, you have to take another danger, mostly high-availability related, into account.

When using a distributed Memcached server configuration, it is important to understand that it works as sharded spaces configuration. That is, it doesn’t store the same sessions over on the various available Memcached server. It only stores each single session in one single server. The decision of where it will store the session is out of the context of this article, but it means that, if you have 300 users with active sessions on your system at any one time, and one of your web servers goes down, you still have 2 web servers and 2 Memcached servers, but ultimately around 100 users will loose their session (that was stored on the web server that went down).

Worst: the PHP configuration will not understand this, and still try to send sessions to the server that was considered to hold these 100 sessions, making it impossible for the users to login again until the corresponding Memcached server is back up (unless you change the configuration in your PHP configuration).

This is why you have to consider 2 things, and why this article is just one step in the right direction:

  • you should configure the Memcached servers from inside your application for the sessions management (as such, you should have a save_handler defined inside it and check for the availability of each server *before* you store the session in it)
  • if your sessions are critical, you should always have some kind of data persistence mechanism, whereby (for example), you store the session in the database once every ten times it is modified

We hope this was of some use to you in understanding how to use Memcached for sessions storage in PHP. Please don’t hesitate to leave questions or comments below.

Howto: Configuring session expiry time in Chamilo 1.9

We seldom receive a request from users of Chamilo LMS saying their sessions are cut in the middle of their activity. And sure, it might so happen that you are in the middle of the redaction of a very large answer to an open question, or diserting on how the course is going to help you in the forum. And we get that it’s super-frustrating to click “submit” and then get an error page. We do, really.

Now on our side of the fence, we have to cover a series of non-trivial issues…

If you leave a session open for too long, another user might hack your session and get inside the system in your place (unless you use an unflawed SSL certificate to protect your communication).This is generally OK if you are a student, but what if you are an administrator or if you are viewing super-confidential learning content?

Another issue you might have is leaving a public computer without closing your session, and have someone else “follow you” and use your session. This leads to the same problem as above. Finally, not cleaning the sessions from time to time inevitably leads to thousands (or rather hundreds of thousands) of sessions being handled by the server, which inevitably leads to a slow server.

So in the best interest of all, it is important to have a balanced session time. We generally consider that 2 hours is a reasonnable total time. If you’ve been inactive for two hours, then it’s reasonable to get disconnected when you come back. ’cause honestly, you weren’t really studying, were you?

But even with that, we still got complaints from the users, so we decided to put it at 100 hours. OK, so that’s 4 days and 4 hours. Enough, right? That’s the default setting in Chamilo, and you can find it in your main/inc/conf/configuration.php on the line that says:

// Session lifetime
$_configuration['session_lifetime'] = 360000;

That’s the value that comes with a default installation of Chamilo 1.9.*.

Now, even if you have that, and depending on your PHP settings for session handling, you might still need to change two settings, but these are out of Chamilo’s control, directly into PHP’s configuration:

session.gc_maxlifetime = 1440

This is the number of seconds after which the garbage collector (a vacuum cleaner, kind of) of PHP considers that session files, on the server, if left untouched, will be erased, and

session.cookie_lifetime = 0

which defines the time (in seconds) that the cookie will ask to the browser to be stored for. If 0, it means that it will stay there until the browser is closed. If anything more than 0, it will stay there for that number of seconds.

Now… the funny thing here (which makes it even harder to track) is that if you use the default session handler of PHP, called “file”, the time used for the garbage collector to erase the sessions files is *not* the last time the session was accessed, but rather the last time the session was *modified*. This means that, if you have some AJAX block refreshing every 30 seconds or so, this will *not* maintain the session active, unless this AJAX refresh actually modifies something to be stored in the session.

So there are many factors to take into account. Our preferred/recommended setup?

$_configuration['session_lifetime'] = 10800; // 3h

Then, in your PHP config:

session.gc_maxlifetime = 10800
session.cookie_lifetime = 0

But now you know how it works, you can tune it as well.

Kudos to Gumbo on Stackoverflow for the missing bits: http://stackoverflow.com/questions/520237/how-do-i-expire-a-php-session-after-30-minutes

Categories: Chamilo, English, Techie Tags: , , ,

Creating new tasks in chamilo course-sessions

In the category of little scripts that can make your life easier when managing huge Chamilo portals, this is a little one that creates one tasks-folder called “ALP” for the “Assignments” (work) tool in each active course for a portal where you have thousands of sessions, with one course per session.

The script works for version 1.9.8 of Chamilo, but might need some adaptations to run on an older version, in particular considering the addDir() function from the main/work/work.lib.php library which was created there recently.

You should put the file into any “one-level” directory under the Chamilo base, so that it can find “../main/inc/global.inc.php”, and then run it on the command line using php5-cli, or load it from a webserver accessing it directly by its path.

The script only affects active sessions (sessions with a start and end dates that are “around” the current date).

The file is downloadable as a .doc file for safety purposes. Download it and rename it to “.php” before you open it, otherwise you’ll get an error: create_tasks


<?php
/**
* This script creates tasks directories in each course, at the session level,
* only for sessions active at the time of running the script
* In order for the script to set the right permissions, it has to be launched
* either as www-data or root
* @author Yannick Warnier
*/
require_once '../main/inc/global.inc.php';
require_once '../main/work/work.lib.php';
$date = date('Y-m-d h:i:s');
$workName = 'ALP';
$courseInfos = array();

/**
* Get the sessions list
*/
$sd = 'date_start';
$ed = 'date_end';
$sql = "SELECT id FROM session WHERE $sd '$date'";
$res = Database::query($sql);
if ($res === false) {
die("Error querying sessions: ".Database::error($res)."\n");
}
/**
* Get the course-session couple
*/
$sessionCourse = array();
while ($row = Database::fetch_assoc($res)) {
$sql2 = "SELECT c.id AS cid, c.code as ccode FROM course c, session_rel_course s WHERE s.id_session = ".$row['id']." AND s.course_code = c.code";
$res2 = Database::query($sql2);
if ($res2 === false) {
die("Error querying courses for session ".$row['id'].": ".Database::error($res2)."\n");
}
if (Database::num_rows($res2) > 0) {
while ($row2 = Database::fetch_assoc($res2)) {
$sessionCourse[$row['id']] = $row2['cid'];
if (empty($courseInfos[$row2['ccode']])) {
$courseInfos[$row2['cid']] = api_get_course_info($row2['ccode']);
}
}
}
}
/**
* Now create the tasks using the addDir function
*/
foreach ($sessionCourse as $sid => $cid) {
$sql = "SELECT id, title FROM c_student_publication
WHERE filetype = 'folder'
AND c_id = $cid
AND session_id = $sid";
$res = Database::query($sql);
if ($res === false) {
echo "Error querying table c_student_publication: $sql\n";
echo "The error message was: ".Database::error($res)."\n";
continue;
}
if (Database::num_rows($res) > 0) {
//Task found, skip
$row = Database::fetch_assoc($res);
echo "Task ".$row['title']." already found in course $cid, session $sid\n";
continue;
}
$params = array(
'new_dir' => $workName,
'description' => '',
'qualification' => 0,
'weight' => 0,
'allow_text_assignment' => 0
);
$res = addDir($params, 1, $courseInfos[$cid], null, $sid);
if ($res === false) {
echo "Could not create task $workName in course $cid, session $sid, for some reason\n";
} else {
echo "Task $workName created in course $cid, session $sid. Task ID is $res\n";
}
}
echo "All done!\n";

Categories: Chamilo, English, php, Techie Tags: , ,

On PHP and cache slams and solutions

December 16, 2013 Leave a comment

While reading about Doctrine’s cache mechanism (which applies to other stuff than database queries, by the way), my eye was caught by a little message at the end (last section) about cache slams.

I have used cache mechanisms extensively over the last few years, but (maybe luckily) never happened to witness a “cache slam”.
There’s a link to a blog (by an unnamed author) that explains that.

To make it short, you can have race conditions in APC (and probably in other caching mechanisms in PHP) when you assign a specific time for expiry of cache data, and a user gets to that expiry time at the same time (or very very very closely) as other users. This provokes a chain reaction (a little bit like an atomic bomb, but not with the same effect – unless some crazy military scientist binds a high-traffic website to the initiation process of an atomic bomb) which makes your website eat all memory and freeze (or something like that).

In reply to me mentioning it on Twitter, @PierreJoye (from the PHP development team) kindly pointed me to APCu, which is a user-land caching mechanism (or so to speak an APC without the opcode, and simplified).

Apparently, this one doesn’t have the cache slam issue (although I haven’t checked it myself, I have faith in Pierre here) and it’s already in PECL (still in beta though), so if you want to try it out on Debian/Ubuntu, you will probably be able to sort it out with a simple:

sudo apt-get install php5-dev php5-pear make
sudo pecl install APCu

(and then configure your PHP to include it).

Don’t forget that it is a PECL library, meaning it’s most likely you’ll have to recompile your PHP to enable it, but PECL should handle that just fine (in our case it’s a bit more complicated if we want to avoid asking our users – of Chamilo, that is – for more dependencies).

Anyway, just so you know, there are people like that who like to make the world a better place for us, PHP developers, so that we can make the world a better place developing professional-grade, super-efficient free software in PHP! Don’t miss out on contributing to that!

PHP’s @ hurts performance

September 25, 2013 2 comments

Did you know…?

As a “take away” information taken from this post https://gist.github.com/nikic/6699370 (by famous PHP core developer @nikita_ppv), it appears like the @ sign in PHP (used to “hide” errors, as the “error-suppression operator” it is) also disables the “compiled variables” optimization (OpCode caching).

Gallery 2.3 not translating

Just in case you would fall upon this issue as well, Gallery2 requires the corresponding language-specific locales to be installed on the system (it depends on them to show the matching translated terms).

The code to do that (and to debug it) is located (in Gallery 2.3) on line 330 of modules/core/classs/GalleryTranslator.class.

We hope it helps you a little.

Categories: English, Techie Tags: , , , ,

PHP’s call_user_func_array() is slow

February 3, 2013 Leave a comment

I just solved an issue that had me waking up in the middle of the night for weeks (just pushing it a bit) and I have to thank PHP’s community and in particular a guy named Brad Proctor for writing a very short but very important comment in the PHP manual, and I quote:

This function is relatively slow (as of PHP 5.3.3) and if you are calling a method with a known number of parameters it is much faster to call it this way:

$class->{$method}($param1, $param2);

He mentions it for PHP 5.3.3, but this is true for PHP 5.3.10 and probably for 5.4 (haven’t tested that one yet).

And indeed, it might be a bit difficult to catch this, but we developed a migration system from external databases to Chamilo LMS, and we wanted to make it very flexible, so what we did was build a configuration file (a sort of dictionary) for which content of which tables went into which other tables. In doing this, we also indicated a “converter function” for each type of data.

So the script being kind of generic and all, th easiest way to call the right function when desired was to simply use call_user_func_array().

Everything went well for the first tables. That’s when we came to one big table (6 million rows) and the migration started to take a very long time. It still took about 0.3s per row, but multiply this by 6 million and you get about 55 days of migration script running without a pause. That’s really difficult to deal with. More even when you hav to deliver within 28 days.

Now the problem is that finding it is a little bit tricky, because putting some pofiling mechanism in place will obviously slow down the operation, and because there is a considerable amount of “previous data” that needs to be inserted before we get to the real problem (thus making us wait for almost hours before seeing the right profiling results).

It is even more tricky when seeing that the real time loss is occurring between the end of the function’s execution and the handling over of the control to the calling script/function…

So the process would be something like this:

0.5000s  Calling call_user_func_array(‘function_a’,$params); from general context

0.5001s  Inside function_a()

0.5011s  Executed something relatively complex inside function_a()

0.5012s Ending function_a()

0.8315s Next line of call_user_func_array() call in general context

That’s right, 0.33s lost just to recover the general context (in my case). That means you’ll loose that amount of time each time you call call_user_func_array().

A good reason to avoid it, just by creating a switch() {…} on the function name, for example.

By calling the function directly (within a switch), the results have been speeded up to something like this:

0.5000s  Calling function_a($params); from general context

0.5001s  Inside function_a()

0.5011s  Executed something relatively complex inside function_a()

0.5012s Ending function_a()

0.5014s Next line of call_user_func_array() call in general context

So, in short, if you can avoid it, avoid using call_user_func_array() at all cost!

Update: thanks to @marvil07 for pointing me towards the same kind of discussion on call_user_func_array() in Drupal. Apparently, they came to the same conclusion that it should be avoided, if at all possible.