original_string: public function join($tables = null, $types = [], $overwrite = false)
      {
          if ($tables === null) {
              deprecationWarning('Using Query::join() to read state is deprecated. Use clause("join") instead.');
  
              return $this->_parts['join'];
          }
  
          if (is_string($tables) || isset($tables['table'])) {
              $tables = [$tables];
          }
  
          $joins = [];
          $i = count($this->_parts['join']);
          foreach ($tables as $alias => $t) {
              if (!is_array($t)) {
                  $t = ['table' => $t, 'conditions' => $this->newExpr()];
              }
  
              if (!is_string($t['conditions']) && is_callable($t['conditions'])) {
                  $t['conditions'] = $t['conditions']($this->newExpr(), $this);
              }
  
              if (!($t['conditions'] instanceof ExpressionInterface)) {
                  $t['conditions'] = $this->newExpr()->add($t['conditions'], $types);
              }
              $alias = is_string($alias) ? $alias : null;
              $joins[$alias ?: $i++] = $t + ['type' => QueryInterface::JOIN_TYPE_INNER, 'alias' => $alias];
          }
  
          if ($overwrite) {
              $this->_parts['join'] = $joins;
          } else {
              $this->_parts['join'] = array_merge($this->_parts['join'], $joins);
          }
  
          $this->_dirty();
  
          return $this;
      }
code: public function join($tables = null, $types = [], $overwrite = false)
      {
          if ($tables === null) {
              deprecationWarning('Using Query::join() to read state is deprecated. Use clause("join") instead.');
  
              return $this->_parts['join'];
          }
  
          if (is_string($tables) || isset($tables['table'])) {
              $tables = [$tables];
          }
  
          $joins = [];
          $i = count($this->_parts['join']);
          foreach ($tables as $alias => $t) {
              if (!is_array($t)) {
                  $t = ['table' => $t, 'conditions' => $this->newExpr()];
              }
  
              if (!is_string($t['conditions']) && is_callable($t['conditions'])) {
                  $t['conditions'] = $t['conditions']($this->newExpr(), $this);
              }
  
              if (!($t['conditions'] instanceof ExpressionInterface)) {
                  $t['conditions'] = $this->newExpr()->add($t['conditions'], $types);
              }
              $alias = is_string($alias) ? $alias : null;
              $joins[$alias ?: $i++] = $t + ['type' => QueryInterface::JOIN_TYPE_INNER, 'alias' => $alias];
          }
  
          if ($overwrite) {
              $this->_parts['join'] = $joins;
          } else {
              $this->_parts['join'] = array_merge($this->_parts['join'], $joins);
          }
  
          $this->_dirty();
  
          return $this;
      }
code_tokens: public function join ( $ tables = null , $ types = [ ] , $ overwrite = false ) { if ( $ tables === null ) { deprecationWarning ( 'Using Query::join() to read state is deprecated. Use clause("join") instead.' ) ; return $ this -> _parts [ 'join' ] ; } if ( is_string ( $ tables ) || isset ( $ tables [ 'table' ] ) ) { $ tables = [ $ tables ] ; } $ joins = [ ] ; $ i = count ( $ this -> _parts [ 'join' ] ) ; foreach ( $ tables as $ alias => $ t ) { if ( ! is_array ( $ t ) ) { $ t = [ 'table' => $ t , 'conditions' => $ this -> newExpr ( ) ] ; } if ( ! is_string ( $ t [ 'conditions' ] ) && is_callable ( $ t [ 'conditions' ] ) ) { $ t [ 'conditions' ] = $ t [ 'conditions' ] ( $ this -> newExpr ( ) , $ this ) ; } if ( ! ( $ t [ 'conditions' ] instanceof ExpressionInterface ) ) { $ t [ 'conditions' ] = $ this -> newExpr ( ) -> add ( $ t [ 'conditions' ] , $ types ) ; } $ alias = is_string ( $ alias ) ? $ alias : null ; $ joins [ $ alias ? : $ i ++ ] = $ t + [ 'type' => QueryInterface :: JOIN_TYPE_INNER , 'alias' => $ alias ] ; } if ( $ overwrite ) { $ this -> _parts [ 'join' ] = $ joins ; } else { $ this -> _parts [ 'join' ] = array_merge ( $ this -> _parts [ 'join' ] , $ joins ) ; } $ this -> _dirty ( ) ; return $ this ; }
docstring: Adds a single or multiple tables to be used as JOIN clauses to this query.
  Tables can be passed as an array of strings, an array describing the
  join parts, an array with multiple join descriptions, or a single string.
  
  By default this function will append any passed argument to the list of tables
  to be joined, unless the third argument is set to true.
  
  When no join type is specified an `INNER JOIN` is used by default:
  `$query->join(['authors'])` will produce `INNER JOIN authors ON 1 = 1`
  
  It is also possible to alias joins using the array key:
  `$query->join(['a' => 'authors'])` will produce `INNER JOIN authors a ON 1 = 1`
  
  A join can be fully described and aliased using the array notation:
  
  ```
  $query->join([
  'a' => [
  'table' => 'authors',
  'type' => 'LEFT',
  'conditions' => 'a.id = b.author_id'
  ]
  ]);
  // Produces LEFT JOIN authors a ON a.id = b.author_id
  ```
  
  You can even specify multiple joins in an array, including the full description:
  
  ```
  $query->join([
  'a' => [
  'table' => 'authors',
  'type' => 'LEFT',
  'conditions' => 'a.id = b.author_id'
  ],
  'p' => [
  'table' => 'publishers',
  'type' => 'INNER',
  'conditions' => 'p.id = b.publisher_id AND p.name = "Cake Software Foundation"'
  ]
  ]);
  // LEFT JOIN authors a ON a.id = b.author_id
  // INNER JOIN publishers p ON p.id = b.publisher_id AND p.name = "Cake Software Foundation"
  ```
  
  ### Using conditions and types
  
  Conditions can be expressed, as in the examples above, using a string for comparing
  columns, or string with already quoted literal values. Additionally it is
  possible to use conditions expressed in arrays or expression objects.
  
  When using arrays for expressing conditions, it is often desirable to convert
  the literal values to the correct database representation. This is achieved
  using the second parameter of this function.
  
  ```
  $query->join(['a' => [
  'table' => 'articles',
  'conditions' => [
  'a.posted >=' => new DateTime('-3 days'),
  'a.published' => true,
  'a.author_id = authors.id'
  ]
  ]], ['a.posted' => 'datetime', 'a.published' => 'boolean'])
  ```
  
  ### Overwriting joins
  
  When creating aliased joins using the array notation, you can override
  previous join definitions by using the same alias in consequent
  calls to this function or you can replace all previously defined joins
  with another list if the third parameter for this function is set to true.
  
  ```
  $query->join(['alias' => 'table']); // joins table with as alias
  $query->join(['alias' => 'another_table']); // joins another_table with as alias
  $query->join(['something' => 'different_table'], [], true); // resets joins list
  ```
  
  @param array|string|null $tables list of tables to be joined in the query
  @param array $types associative array of type names used to bind values to query
  @param bool $overwrite whether to reset joins with passed list or not
  @see \Cake\Database\Type
  @return $this|array
docstring_tokens: Adds a single or multiple tables to be used as JOIN clauses to this query . Tables can be passed as an array of strings an array describing the join parts an array with multiple join descriptions or a single string .