Como converter uma matriz para objeto em PHP?


367

Como posso converter uma matriz como esta em objeto?

[128] => Array
    (
        [status] => Figure A.
 Facebook's horizontal scrollbars showing up on a 1024x768 screen resolution.
    )

[129] => Array
    (
        [status] => The other day at work, I had some spare time
    )

4
Que tipo de objeto você deseja obter, exatamente? O que quero dizer é: quais devem ser os atributos?
— Pascal MARTIN

em um circuito que deveria tanto porque o estado é uma matriz de dados para que ambos são printet
— streetparade

não tenho certeza de quando, mas isso simplesmente funciona:$a = (object)['hello' => 'world'];
— Nishchal Gautam 5/11

Respostas:


588

No caso mais simples, provavelmente é suficiente "converter" a matriz como um objeto:

$object = (object) $array;

Outra opção seria instanciar uma classe padrão como uma variável e percorrer sua matriz ao atribuir novamente os valores:

$object = new stdClass();
foreach ($array as $key => $value)
{
    $object->$key = $value;
}

Como Edson Medina apontou, uma solução realmente limpa é usar as json_funções internas:

$object = json_decode(json_encode($array), FALSE);

Isso também (recursivamente) converte todas as suas sub-matrizes em objetos, que você pode ou não querer. Infelizmente, ele apresenta um desempenho de 2-3x na abordagem de loop.

Atenção! (obrigado ao Ultra pelo comentário):

O json_decode em diferentes ambientes converte dados UTF-8 de maneiras diferentes. Acabo recebendo valores '240.00' localmente e '240' em produção - grande desastre. Morover se a conversão falhar, get de string retornado como NULL


41
"como as variáveis ​​não podem começar com números", sim, elas podem: $ object -> {3} = 'xyz';
— Chelmertz

11
"tem um impacto no desempenho de 2-3x". Essa é uma comparação injusta, pois o último método retorna objetos recursivos, enquanto a abordagem de loop sem condições adicionais (como na resposta de @streetparade) apenas converte o primeiro nível.
— Fee13

8
@feeela Eu não acho que seja injusto. Eu mencionei que ele faz a conversão recursivamente. Além disso, o hit 2-3x desempenho se chegou a utilizar uma matriz de entrada plana (que não usaria qualquer recursão)
— JLB

6
ATENÇÃO! O json_decode em diferentes ambientes converte dados UTF-8 de maneiras diferentes. Acabo recebendo valores '240.00' localmente e '240' em produção - grande desastre. Morover se a conversão falhar, get de string retornado como NULL
— Szymon Toda

11
Observe ao usar as funções json_ *: as referências (por exemplo, para outras matrizes) armazenadas na matriz original serão duplicadas neste caso. Digamos que a chave xna matriz tenha uma referência a outra matriz. Então, $object->xapós a execução do seu one-liner, haverá uma duplicata $array['x'], e não mais uma referência à matriz original. Isso pode ser inofensivo em alguns aplicativos, mas para matrizes grandes ele desperdiça memória e pode atrapalhar a execução se a referência estiver sendo usada posteriormente.
— O Coprolal

153

você pode simplesmente usar a conversão de tipos para converter uma matriz em objeto.

// *convert array to object* Array([id]=> 321313[username]=>shahbaz)
$object = (object) $array_name;

//now it is converted to object and you can access it.
echo $object->username;

107

O caminho mais fácil seria

$object = (object)$array;

Mas não é isso que você quer. Se você deseja objetos, deseja obter algo, mas está faltando nesta pergunta. Usar objetos apenas pelo motivo de usar objetos não faz sentido.


2
não funciona, eu fiz isso antes que eu fiz a pergunta aqui, então deve haver outra maneira de doit
— streetparade

20
Por que ele tem que dar sua razão para querer usar objetos? Eu não acho que isso seja relevante para como é feito. Talvez ele precise codificá-los ou serializá-los? Pode haver dezenas de razões para fazer isso.
— zombat

hmm .. eu olhei para a saída do navegador que se parece com este objeto (stdClass) # 150 (130) {[0] => array (1) {["status"] => string (130) "No último Mac e Os usuários do Linux não precisam se sentir como cidadãos de segunda classe no país do Chrome: eles têm versão beta oficial ... "} oficialmente, isso é um objeto, mas como iterar isso para que eu possa acessar status como $ obj-> status qualquer idéia ?
— Streetparade 08/12/2009

No zombat, a codificação JSON não é motivo para usar um objeto; existe um sinalizador para json_encode () para usar objetos. com serializar, seria necessário um tipo de objeto específico esperado pelo receptor. E, em geral, tento ajudar com o problema real . para mim, essa pergunta implica que há um erro de arquitetura em outro lugar.
— Johannes

nice one, o trabalho com variedade laravel a questão objeto
— Anthony Kal

105

Corte rápido:

// assuming $var is a multidimensional array
$obj = json_decode (json_encode ($var), FALSE);

Não é bonito, mas funciona.


2
Na verdade, eu amo essa solução, usar funções internas em vez de definidas pelo usuário é sempre mais rápido, e isso funciona muito bem. Obrigado pela dica.
— aknatn

@ Oddant Isso resolve o problema mencionado acima (converta uma matriz em um objeto). Seu discurso deve ser direcionado para o post principal, não para a minha solução.
— Edson Medina

@EdsonMedina eu fiz, meu post é muito baixo, no entanto.
— vdegenne

11
@Oddant, para ser justo com o @EdsonMedina, a pergunta original não especifica de que visibilidade os atributos precisam, e como o OP não usa $thisnos comentários que seguem como o acessador, é altamente implícito que ele / ela deseja uma stdClassinstância como saída e não uma classe definida pelo usuário, como a sua resposta usa. Concordo com a elegância dessa solução, mas infelizmente é um padrão bastante empregado para resolver esse problema com matrizes aninhadas nas quais a conversão para o objeto não funcionará. Também é possível que o OP esteja usando uma interface que exija um objeto como entrada e não uma matriz.
— DeaconDesperado

3
Não esqueça que, dessa maneira, você perderá todos, exceto os tipos básicos. DateTime será o conversor stdObject, por exemplo.
— Denis Pshenov

97

Aqui estão três maneiras:

  1. Fingir um objeto real:

    class convert
    {
        public $varible;
    
        public function __construct($array)
        {
            $this = $array;
        }
    
        public static function toObject($array)
        {
            $array = new convert($array);
            return $array;
        }
    }
  2. Converta a matriz em um objeto convertendo-a em um objeto:

    $array = array(
        // ...
    );
    $object = (object) $array;
  3. Converta manualmente a matriz em um objeto:

    $object = object;
    foreach ($arr as $key => $value) {
        $object->{$key} = $value;
    }

2
hmm obrigado, mas sua classe cara dá o seguinte erro fatal de erro: Não é possível reatribuir $ this em /var/www/bot/inc/twitter-bot.php na linha 10
— streetparade

11
e typcasint @ referência is not uma boa idéia, mesmo que não iria trabalhar aqui é o que eu tenho T_OBJECT_CAST inesperado, esperando T_new ou T_STRING ou T_VARIABLE ou '$'
— streetparade

2
$ array = & (objeto) $ array == boa implementação do KISS!
— mate64

16
Por que alguém iria querer usar um método diferente de 2)? Existem desvantagens?
— Yogu

7
typecasting uma matriz em trabalho objecto esquentar em matrizes aninhadas
— minhajul

34

Seu caminho para simples, isso criará um objeto para matrizes recursivas também:

$object = json_decode(json_encode((object) $yourArray), FALSE);

4
passar falsepara json_decode()retornará uma matriz associativa.
— Rust

3
@ user3284463 Passar truepara json_decoderetornará uma matriz associativa, falseé o padrão e retornará uma StdClassinstância.
— 22718 Elliot Reed

23

Dependendo de onde você precisa disso e de como acessar o objeto, existem diferentes maneiras de fazê-lo.

Por exemplo: basta digitar

$object =  (object) $yourArray;

No entanto, o mais compatível é o uso de um método utilitário (ainda não parte do PHP) que implementa a conversão padrão do PHP com base em uma string que especifica o tipo (ou ignorando-o apenas de-referenciando o valor):

/**
 * dereference a value and optionally setting its type
 *
 * @param mixed $mixed
 * @param null  $type (optional)
 *
 * @return mixed $mixed set as $type
 */
function rettype($mixed, $type = NULL) {
    $type === NULL || settype($mixed, $type);
    return $mixed;
}

O exemplo de uso no seu caso ( Demonstração Online ):

$yourArray = Array('status' => 'Figure A. ...');

echo rettype($yourArray, 'object')->status; // prints "Figure A. ..."

17

Este funcionou para mim

  function array_to_obj($array, &$obj)
  {
    foreach ($array as $key => $value)
    {
      if (is_array($value))
      {
      $obj->$key = new stdClass();
      array_to_obj($value, $obj->$key);
      }
      else
      {
        $obj->$key = $value;
      }
    }
  return $obj;
  }

function arrayToObject($array)
{
 $object= new stdClass();
 return array_to_obj($array,$object);
}

uso:

$myobject = arrayToObject($array);
print_r($myobject);

retorna:

    [127] => stdClass Object
        (
            [status] => Have you ever created a really great looking website design
        )

    [128] => stdClass Object
        (
            [status] => Figure A.
 Facebook's horizontal scrollbars showing up on a 1024x768 screen resolution.
        )

    [129] => stdClass Object
        (
            [status] => The other day at work, I had some spare time
        )

como de costume, você pode fazer um loop como:

foreach($myobject as $obj)
{
  echo $obj->status;
}

Mas este é cerca de 500% mais lento (testado) do que a conversão de tipo: $ obj = (object) $ array;
— XZero 13/07

@xZero, mas $obj = (object) $array;não funciona para matrizes multidimensionais.
— Jeff Puckett

15

Não existe um método interno para fazê-lo, até onde eu saiba, mas é tão fácil quanto um loop simples:

    $obj= new stdClass();

    foreach ($array as $k=> $v) {
        $obj->{$k} = $v;
    }

Você pode explicar isso se precisar criar seu objeto recursivamente.


14

Você pode usar a função (objeto) para converter sua matriz em um objeto.

$arr= [128=> ['status'=>
                 'Figure A. Facebook \'s horizontal scrollbars showing up on a 1024x768 screen resolution.'],
                  129=>['status'=>'The other day at work, I had some spare time']];

            $ArrToObject=(object)$arr;
            var_dump($ArrToObject);

O resultado será um objeto que contém matrizes:

objeto (stdClass) # 1048 (2) {[128] => matriz (1) {

["status"] => string (87) "Figura A. As barras de rolagem horizontais do Facebook são exibidas em uma resolução de tela de 1024x768." }

[129] => matriz (1) {["status"] => string (44) "Outro dia no trabalho, eu tinha algum tempo livre"}}


9

Na verdade, se você quiser usar isso com matrizes multidimensionais, poderá usar alguma recursão.

static public function array_to_object(array $array)
{
    foreach($array as $key => $value)
    {
        if(is_array($value))
        {
            $array[$key] = self::array_to_object($value);
        }
    }
    return (object)$array;
}

8

Eu definitivamente iria com uma maneira limpa como esta:

<?php

class Person {

  private $name;
  private $age;
  private $sexe;

  function __construct ($payload)
  {
     if (is_array($payload))
          $this->from_array($payload);
  }


  public function from_array($array)
  {
     foreach(get_object_vars($this) as $attrName => $attrValue)
        $this->{$attrName} = $array[$attrName];
  }

  public function say_hi ()
  {
     print "hi my name is {$this->name}";
  }
}

print_r($_POST);
$mike = new Person($_POST);
$mike->say_hi();

?>

se você enviar:

formulaire

você receberá isto:

Mike

Achei isso mais lógico ao comparar as respostas acima de Objetos que devem ser usadas para o propósito para o qual foram feitas (pequenos objetos fofos encapsulados).

O uso de get_object_vars também garante que nenhum objeto extra seja criado no Objeto manipulado (você não quer um carro com um nome de família nem uma pessoa com 4 rodas).


Por que você não usa $ attr_value em vez de $ array [$ attr_name]; em from_array yourpublic função ($ array) função
— Sakkeer Hussain

7

Você também pode usar um ArrayObject, por exemplo:

<?php
    $arr = array("test",
                 array("one"=>1,"two"=>2,"three"=>3), 
                 array("one"=>1,"two"=>2,"three"=>3)
           );
    $o = new ArrayObject($arr);
    echo $o->offsetGet(2)["two"],"\n";
    foreach ($o as $key=>$val){
        if (is_array($val)) {
            foreach($val as $k => $v) {
               echo $k . ' => ' . $v,"\n";
            }
        }
        else
        {
               echo $val,"\n";
        }
    }
?>

//Output:
  2
  test
  one => 1
  two => 2
  three => 3
  one => 1
  two => 2
  three => 3

11
Na minha opinião, isso deve se tornar a melhor resposta. Mais informações aqui: php.net/manual/en/arrayobject.construct.php
— Julian

7

O que eu uso (é um membro da classe):

const MAX_LEVEL = 5; // change it as needed

public function arrayToObject($a, $level=0)
{

    if(!is_array($a)) {
        throw new InvalidArgumentException(sprintf('Type %s cannot be cast, array expected', gettype($a)));
    }

    if($level > self::MAX_LEVEL) {
        throw new OverflowException(sprintf('%s stack overflow: %d exceeds max recursion level', __METHOD__, $level));
    }

    $o = new stdClass();
    foreach($a as $key => $value) {
        if(is_array($value)) { // convert value recursively
            $value = $this->arrayToObject($value, $level+1);
        }
        $o->{$key} = $value;
    }
    return $o;
}

7

Técnica pouco complicada, mas fácil de estender:

Suponha que você tenha uma matriz

$a = [
     'name' => 'ankit',
     'age' => '33',
     'dob' => '1984-04-12'
];

Suponha que você tenha uma classe Person que pode ter mais ou menos atributos dessa matriz. por exemplo

class Person 
{
    private $name;
    private $dob;
    private $age;
    private $company;
    private $city;
}

Se você ainda deseja alterar sua matriz para o objeto person. Você pode usar a classe ArrayIterator.

$arrayIterator = new \ArrayIterator($a); // Pass your array in the argument.

Agora você tem o objeto iterador.

Crie uma classe estendendo a classe FilterIterator; onde você deve definir o método abstrato Siga o exemplo

class PersonIterator extends \FilterIterator
{
    public function accept()
    {
        return property_exists('Person', parent::current());
    }
}

A impelmentação acima vinculará a propriedade somente se ela existir na classe.

Adicione mais um método na classe PersonIterator

public function getObject(Person $object)
{
        foreach ($this as $key => $value)
        {
            $object->{'set' . underscoreToCamelCase($key)}($value);
        }
        return $object;
}

Verifique se você tem mutadores definidos em sua classe. Agora você está pronto para chamar essas funções onde deseja criar um objeto.

$arrayiterator = new \ArrayIterator($a);
$personIterator = new \PersonIterator($arrayiterator);

$personIterator->getObject(); // this will return your Person Object. 

6

recursão é seu amigo:

function __toObject(Array $arr) {
    $obj = new stdClass();
    foreach($arr as $key=>$val) {
        if (is_array($val)) {
            $val = __toObject($val);
        }
        $obj->$key = $val;
    }

    return $obj;
}

6

Isso requer PHP7 porque eu escolhi usar uma função lambda para bloquear o 'innerfunc' dentro da função principal. A função lambda é chamada recursivamente, daí a necessidade de: "use (& $ innerfunc)". Você poderia fazer isso no PHP5, mas não poderia ocultar o funcionamento interno.

function convertArray2Object($defs) {
    $innerfunc = function ($a) use ( &$innerfunc ) {
       return (is_array($a)) ? (object) array_map($innerfunc, $a) : $a; 
    };
    return (object) array_map($innerfunc, $defs);
}

5

use esta função que eu fiz:

function buildObject($class,$data){
    $object = new $class;
    foreach($data as $key=>$value){
        if(property_exists($class,$key)){
            $object->{'set'.ucfirst($key)}($value);
        }
    }
    return $object;
}

Uso:

$myObject = buildObject('MyClassName',$myArray);

5

um forro

$object= json_decode(json_encode($result_array, JSON_FORCE_OBJECT));

11
Observe que as referências (por exemplo, para outras matrizes) armazenadas na matriz original serão duplicadas por essa linha única. Digamos que a chave xna matriz tenha uma referência a outra matriz. Então, $object->xapós a execução do seu one-liner, haverá uma duplicata $result_array['x'], e não a matriz idêntica.
— The Coprolal

4

Fácil:

$object = json_decode(json_encode($array));

Exemplo:

$array = array(
    'key' => array(
        'k' => 'value',
    ),
    'group' => array('a', 'b', 'c')
);

$object = json_decode(json_encode($array));

Então, o seguinte é verdadeiro:

$object->key->k === 'value';
$object->group === array('a', 'b', 'c')

11
Eu acho que isso é uma solução alternativa. Por que codificar uma matriz para json e depois decodificá-la? Não é uma decisão ótima para mim.
— Julian

11
@ Julian, por funcionar recursivamente, o faz de maneira adequadamente definida e suficientemente confiável ("padrão"), e também é rápido o suficiente para ser uma boa alternativa à mágica aleatória de códigos de macaco codificados à mão.
— Sz.

3

Você também pode fazer isso adicionando (objeto) à esquerda da variável para criar um novo objeto.

<?php
$a = Array
    ( 'status' => " text" );
var_dump($a);
$b = (object)$a;
var_dump($b);
var_dump($b->status);

http://codepad.org/9YmD1KsU


11
talvez valha a pena mencionar que isso é chamado de "conversão" ou "conversão de tipo": php.net/manual/en/… e o comportamento do (objeto) array () está documentado aqui: php.net/manual/en/…
— Pete

2

O uso json_encodeé problemático devido à maneira como ele lida com dados que não são UTF-8. Vale a pena notar que o método json_encode/ json_encodetambém deixa matrizes não associativas como matrizes. Isso pode ou não ser o que você deseja. Recentemente, eu estava na posição de precisar recriar a funcionalidade desta solução, mas sem usar json_funções. Aqui está o que eu vim com:

/**
 * Returns true if the array has only integer keys
 */
function isArrayAssociative(array $array) {
    return (bool)count(array_filter(array_keys($array), 'is_string'));
}

/**
 * Converts an array to an object, but leaves non-associative arrays as arrays. 
 * This is the same logic that `json_decode(json_encode($arr), false)` uses.
 */
function arrayToObject(array $array, $maxDepth = 10) {
    if($maxDepth == 0) {
        return $array;
    }

    if(isArrayAssociative($array)) {
        $newObject = new \stdClass;
        foreach ($array as $key => $value) {
            if(is_array($value)) {
                $newObject->{$key} = arrayToObject($value, $maxDepth - 1);
            } else {
                $newObject->{$key} = $value;
            }
        }
        return $newObject;
    } else {

        $newArray = array();
        foreach ($array as $value) {
            if(is_array($value)) {
                $newArray[] = arrayToObject($value, $maxDepth - 1);
            } else {
                $newArray[] = $value;
            }                
        }
        return $newArray;
    }
}

2

Melhor método do mundo :)

function arrayToObject($conArray)
{
    if(is_array($conArray)){
        /*
        * Return array converted to object
        * Using __FUNCTION__ (Magic constant)
        * for recursive call
        */
        return (object) array_map(__FUNCTION__, $conArray);
    }else{
        // Return object
        return $conArray;
    }
}

se você usar métodos diferentes, terá problemas. Este é o melhor método. Você já viu.


2

Matrizes multidimensionais em um objeto. esse código é usado para a conversão do método try and catch da API de pesquisa do Bing.

try {
        // Perform the Web request and get the JSON response
        $context = stream_context_create($options);
        $results = file_get_contents($url . "?cc=" . $country . "&category=" . $type, false, $context);
        $results = json_decode($results);
        return response()->json($results);
    } catch (\Exception $e) {
        $results = array('value' => array(
                (object) array(
                    "name" => "Unable to Retrive News",
                    "url" => "http://www.sample.com/",
                    "image" => (object) array("thumbnail" => (object) array("contentUrl" => "")),
                    "publishedAt" => "",
                    "description" => "")
            )
        );
        $results = (object) $results;
        return response()->json($results);
    }

2

Você pode usar o Reflection:

<?php

$array = ['name'=>'maria','age'=>33];

class Person {

    public $name;
    public $age;

    public function __construct(string $name, string $age){
        $this->name  = $name;
        $this->age = $age;
    }
}

function arrayToObject(array $array, string $class_name){

    $r = new ReflectionClass($class_name);
    $object = $r->newInstanceWithoutConstructor();
    $list = $r->getProperties();
    foreach($list as $prop){
      $prop->setAccessible(true);
      if(isset($array[$prop->name]))
        $prop->setValue($object, $array[$prop->name]);
    } 

    return $object;

}

$pessoa1 = arrayToObject($array, 'Person');
var_dump($pessoa1);

1

O CakePHP possui uma classe Set :: map recursiva que basicamente mapeia uma matriz para um objeto. Pode ser necessário alterar a aparência da matriz para tornar o objeto da maneira que você deseja.

http://api.cakephp.org/view_source/set/#line-158

Na pior das hipóteses, você poderá obter algumas idéias dessa função.


1

Obviamente, é apenas uma extrapolação das respostas de outras pessoas, mas aqui está a função recursiva que converterá qualquer matriz tridimensional em um objeto:

   function convert_array_to_object($array){
      $obj= new stdClass();
      foreach ($array as $k=> $v) {
         if (is_array($v)){
            $v = convert_array_to_object($v);   
         }
         $obj->{strtolower($k)} = $v;
      }
      return $obj;
   }

E lembre-se que se a matriz teve teclas numéricas eles ainda podem ser referenciados no objeto resultante usando {}(por exemplo: $obj->prop->{4}->prop)


1

Inspirado por todos esses códigos, tentei criar uma versão aprimorada com suporte para: nome específico da classe, evitar método construtor, padrão 'beans' e modo estrito (defina apenas as propriedades existentes):

    class Util {

static function arrayToObject($array, $class = 'stdClass', $strict = false) {
        if (!is_array($array)) {
            return $array;
        }

        //create an instance of an class without calling class's constructor
        $object = unserialize(
                sprintf(
                        'O:%d:"%s":0:{}', strlen($class), $class
                )
        );

        if (is_array($array) && count($array) > 0) {
            foreach ($array as $name => $value) {
                $name = strtolower(trim($name));
                if (!empty($name)) {

                    if(method_exists($object, 'set'.$name)){
                        $object->{'set'.$name}(Util::arrayToObject($value));
                    }else{
                        if(($strict)){

                            if(property_exists($class, $name)){

                                $object->$name = Util::arrayToObject($value); 

                            }

                        }else{
                            $object->$name = Util::arrayToObject($value); 
                        }

                    }

                }
            }
            return $object;
        } else {
            return FALSE;
        }
        }
}

1

Código

Esta função funciona da mesma forma que json_decode(json_encode($arr), false).

function arrayToObject(array $arr)
{
    $flat = array_keys($arr) === range(0, count($arr) - 1);
    $out = $flat ? [] : new \stdClass();

    foreach ($arr as $key => $value) {
        $temp = is_array($value) ? $this->arrayToObject($value) : $value;

        if ($flat) {
            $out[] = $temp;
        } else {
            $out->{$key} = $temp;
        }
    }

    return $out;
}

Teste

Teste 1: matriz plana

$arr = ["a", "b", "c"];
var_export(json_decode(json_encode($arr)));
var_export($this->arrayToObject($arr));

Resultado:

array(
    0 => 'a',
    1 => 'b',
    2 => 'c',
)
array(
    0 => 'a',
    1 => 'b',
    2 => 'c',
)

Teste 2: matriz de objetos

$arr = [["a" => 1], ["a" => 1], ["a" => 1]];
var_export(json_decode(json_encode($arr)));
var_export($this->arrayToObject($arr));

Resultado:

array(
    0 => stdClass::__set_state(array('a' => 1,)),
    1 => stdClass::__set_state(array('a' => 1,)),
    2 => stdClass::__set_state(array('a' => 1,)),
)
array(
    0 => stdClass::__set_state(array('a' => 1,)),
    1 => stdClass::__set_state(array('a' => 1,)),
    2 => stdClass::__set_state(array('a' => 1,)),
)

Teste 3: Objeto

$arr = ["a" => 1];
var_export(json_decode($arr));
var_export($this->arrayToObject($arr));

Resultado:

stdClass::__set_state(array('a' => 1,))
stdClass::__set_state(array('a' => 1,))

0

eu fiz isso de maneira bem simples,

    $list_years         = array();
    $object             = new stdClass();

    $object->year_id   = 1 ;
    $object->year_name = 2001 ;
    $list_years[]       = $object;
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.