código mantenible, en wordpress
TRANSCRIPT
De qué va esta charlaIntroducción a los conceptos básicos de desarrollo con WordPress
Abrir perspectiva sobre buenas prácticas sobre este CMS
Mostrar código mantenible, cercano al que crearíamos con Symfony2, Silex o Laravel
/meAsier Marqués
simettric.com & 4visionshq.com
linkedin.com/in/asier
@asiermarques
• techcrunch.com
• thenewyorker.com
• BBC America
• Time Inc
• Fortune
Beneficios
• Es un CMS muy conocido, muchos usuarios ya saben utilizar su backoffice
• Existe una gran cantidad de plugins
• Existe una gran cantidad de themes
• Un mercado amplio
Puntos negativos• Una base de desarrollo obsoleta
• Gran cantidad de plugins que no tienen ningún tipo de requisito de calidad
• Atado a un esquema de base de datos concreto
• Para proyectos cuyo core de negocio se salga de contenido, puede quedarse corto sin un coste de mantenimiento elevado
¿De qué se compone Wordpress?
• Plugins y Themes
• Filters y actions
• WP_Query y loop
• Custom post type y taxonomies
MultiSite
• WordPress es multisite
• Disponemos de funciones, actions y filters específicos para gestionar los blogs dentro de un network
• Casi todas esas funciones son las originales del proyecto Wordpress Mu
http://codex.wordpress.org/WPMU_Functions
wpmu_create_blog($domain, $path, $title, $user_id, $meta, $site_id)
Backoffice• Nos permite gestionar nuestro sitio y nuestro contenido
• Es totalmente personalizable, podemos
• Añadir secciones en el menú
• Añadir columnas en las tablas de contenido
• Añadir campos, y metaboxes, para usuarios, contenido y terms
• Añadir campos y opciones a los menús dinámicos de themes
Post Types• Todo es un Post
• Por defecto tenemos contenido de tipo post, page y attachment.
• El contenido puede ser jerárquico
• El contenido tiene un archivo
• El contenido puede tener metainformación
Custom post typeregister_post_type( 'book', array(
'labels' => array(“name” => “Libros”),
'public' => true,
'rewrite' => array( 'slug' => 'books' ),
'has_archive' => true,
'hierarchical' => false,
'supports' => array( 'title', 'editor', 'thumbnail', 'comments' )
);
Custom post type$product = get_post( $product_id );
$product_color = get_post_meta( $product, “color”, true);
$products = get_posts( array( “post_type” => “product” ) );
foreach( $products as $product ){
echo get_the_title( $product );
}
Taxonomies
• Todas las taxonomias son terms
• Por defecto tenemos los term “tag” y “category”
• Los term pueden ser jerárquicos o no
• Por defecto no hay soporte de metainformación
Custom taxonomyregister_taxonomy( ‘author’, 'book', array(
'hierarchical' => false,
'labels' => array( “name”=> “Autor” ),
'show_admin_column' => true,
'update_count_callback' => '_update_post_term_count',
'rewrite' => array( 'slug' => ‘authors’ ),
));
Custom Taxonomy$terms = get_terms(“talla”, array(“order_by”=>”count”));
foreach( $terms as $term ){
echo $term->name;
}
$product_terms = wp_get_post_terms( $product, “talla” );
Obtener enlacesget_permalink($post);
get_term_link($term, “color”);
get_post_type_archive_link( “post_type” )
wp_get_attachment_url( $post->ID );
$src_array = wp_get_attachment_image_src($post->ID, “size”);
$image_src = $src_array[0];
Plugins vs Themes• La responsabilidad principal de un theme es la de
dotar de un diseño al sitio web
• La responsabilidad de un plugin es la de dotar funcionalidad y personalización, sin acoplarse a un theme especifico
• Un theme puede actuar adicionalmente como plugin
• Los plugins pueden personalizar las funciones core de tipo “pluggables”
Los archivos más importantes en un theme
• styles.css
• index.php
• header.php y footer.php
• functions.php
index.phpget_header(“key”); //header-key.php
if ( have_posts() ) {
while ( have_posts() ) {
the_post();
//nuestro código
}
}
get_footer(“key”); //footer-key.php
Template Hierarchyarchive-producto.php -> archive.php -> index.php
single-producto.php -> single.php -> index.php
taxonomy-color.php -> taxonomy.php -> index.php
search.php -> index.php
404.php -> index.php
front-page -> index.php
home.php -> index.php
Loop<?php
if ( have_posts() ) {
while ( have_posts() ) {
the_post(); ?>
<h2><?php the_title() ?></h2>
<p><?php the_content() ?></p>
<?php }
}
En realidad es un WP_Query<?php
global $wp_query;
if ( $wp_query->have_posts() ) {
while ( $wp_query->have_posts() ) {
$post = $wp_query->the_post(); ?>
<h2><?php echo get_the_title($post) ?></h2>
<p><?php echo get_the_content($post) ?></p>
<?php }
}
Actions y Filters
• Nos podemos suscribir a ellos para modificar y extender Wordpress
• Los actions actúan como eventos a los que nos suscribimos para realizar una operación.
• Los filters también son eventos, pero nos permiten manipular información o html del template.
Request y ejecución1. Se recibe la petición HTTP
2. El sistema de routing hace el match de la ruta que coincida con la url de la petición
3. Se crea el objeto WP_Query y las query_vars
4. En base al WP_Query se localiza el template
5. Se retorna el resultado
http://codex.wordpress.org/Plugin_API/Action_Reference
Request y ejecución1. /category/animales = index.php?category=animales
2. new WP_Query( array( “category” => “animales”) );
3. theme/category.php
Rutas personalizadasadd_action('init', function() {
add_rewrite_rule(
‘^tasks/([^/]*)/([^/]*)/?’,
’index.php?post_type=“task”&category=$matches[1]&custom=$matches[2]',
‘top'
);
add_rewrite_tag('%custom%', '(([^/]*))');
});
Rutas personalizadasAl crear la WP_Query, se incluyen los anteriores parámetros:
$wp_query->query_vars[“post_type”]
$wp_query->query_vars[“category”]
$wp_query->query_vars[“custom”]
QueriesWP_Query $wp_query http://codex.wordpress.org/Class_Reference/WP_Query
wpdb $wpdb
http://codex.wordpress.org/Class_Reference/wpdb
$wp_query = new WP_Query(array(
“post_type” => “producto”,
“nombre_taxonomy” => “valor”,
“posts_per_page” => 20,
“meta_query” => array(
“relation” => “OR”,
array( “meta_key” => “color”, “meta_value” => “azul” ),
array( “meta_key” => “precio”,
“meta_value” => array(20, 200),
“compare” => “BETWEEN”,
“type” => numeric ) )
));
$wp_query = new WP_Query(array(
“post_type” => “producto”,
“nombre_taxonomy” => “valor”,
“posts_per_page” => 20,
“meta_query” => array(
“relation” => “OR”,
array( “meta_key” => “color”, “meta_value” => “azul” ),
array( “meta_key” => “precio”,
“meta_value” => array(20, 200),
“compare” => “BETWEEN”,
“type” => numeric ) )
));
WP_Query//obtenemos el SQL de la consulta $sql = $wp_query->request;
//obtenemos los posts de resultados de la consulta $posts = $wp_query->get_posts();
WPDB$wpdb->prepare( "SELECT * FROM table WHERE ID = %d", $id );
$wpdb->update( $table, $data_array, $where_array);
$wpdb->delete( $table, $where_array );
Templates personalizadasadd_filter( 'template_include', function ( $template ) {
if ( is_page( get_option(“landing_page_id”) ) ) {
$template = __DIR__ . “/../Templates/landing.php“;
}
return $template;
}
function replaceQuery(WP_Query $wp_query){
if( get_option(“landing_page_id”) ){
//cambiamos el loop de la portada
$wp_query = new WP_Query(array( “category” => 1 ));
//evitamos un loop infinito
remove_action(“replaceQuery”);
}
};
add_action(“pre_get_posts”, “replaceQuery”);
Ajax: En el Backendadd_action('wp_head',function() {
?>
<script type="text/javascript">
var ajaxurl = '<?php echo admin_url('admin-ajax.php'); ?>';
</script>
<?php
});
add_action( ‘wp_ajax_controller.action’, “callback”);
add_action( 'wp_ajax_nopriv_controller.action’, “callback”);
Herramientas• Gestión de librerías: composer
• Inyección de dependencias: Symfony o Pimple
• Configuración: Yaml y Configuration Component
• Assets: Assetic
• Templates: Twig
ComposerWordPress no ofrece gestión de librerías ni componentes por Composer
Por suerte existe el proyecto wpackagist.org
Inyección de dependenciasNos permite hacer más portable y desacoplado nuestro código.
• Pimple
• Symfony Dependency Injection
Inyección de dependencias$container[“wp_query”] = function ($c) use ($wp_query) {
return $wp_query;
};
$container[“blog.repository”] = function ($c) {
return new WPBlogRepository($c[“wp_query”]);
};
$container[“blog.model”] = function(){
return new BlogModel( $c[“blog.repository”] );
}
Modelo Portableclass BlogModel {
private $_repository;
function __construct(RepositoryInterface $adapter){
$this->_repository = $adapter;
}
function getPosts($page, $limit=20){
return $this->_repository->getPosts($page, $limit);
}
}
Assets
• Wordpress nos permite registrar y encolar scripts (y css) gestionando sus dependencias
• Al llamar al wp_head o wp_footer, los scripts y hojas de estilos se añaden de forma automática al html de la página
Assets wp_enqueue_script( ‘bootstrap',
get_template_directory_uri() . '/assets/js/bootstrap.min.js',
array('jquery'),
'3.0.0',
$in_footer = true
);
Assetic• Assetic nos permite gestionar de forma más
avanzada los assets, uniéndolos y optimizándolos para servirlos en la página.
• Mediante el plugin WPAssetic podemos disponer de esta utilidad en nuestro sitio
WP CronAl activar un plugin o theme, nos permite disparar eventos con una periodicidad concreta
register_activation_hook( __FILE__, function() {
wp_schedule_event( time(), 'hourly', ‘evento_cada_hora’ );
});
add_action( ‘evento_cada_hora', function(){
});
Rendimiento con Varnish• Varnish es un servicio de proxy-cache que permite
servir el resultado del html desde memoria
• Podemos actualizar e invalidar la caché de nuestro contenido de forma automática mediante el plugin wp varnish
Pluggable• WordPress permite sobreescribir algunas
funciones internas desde nuestros plugins.
• Dichas funciones se denominan pluggables
Pluggable Emailsif ( !function_exists('wp_new_user_notification') ) { function wp_new_user_notification( $user_id, $plaintext_pass = '' ) {
wp_email( “[email protected]”, “asunto”, “cuerpo del mensaje” );
}
}
CachéWordpress tiene una cache clave-valor mediante un conjunto de funciones llamadas transients
Utilizan de forma interna el objeto wp_object_cache
set_transient( ‘datos_cacheados',
$datos,
12 * HOUR_IN_SECONDS );
TDDWordPress test framework:
https://github.com/nb/wordpress-tests.git
WP Mock
https://github.com/10up/wp_mock
http://slides.com/jlopezmo/test-driven-development-in-wordpress